From b39e43f8d928760537276afe26121bd3aad57c35 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sun, 24 Aug 2025 18:09:02 +0200 Subject: [PATCH 01/17] 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 | 45 +- .../rustc_lint/src/types/improper_ctypes.rs | 432 +++++++++++++----- 2 files changed, 361 insertions(+), 116 deletions(-) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index a9d67ff4a1d4e..889cba5c9831d 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2348,13 +2348,40 @@ 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>) { + let add_args = |msg: DiagMessage| { + let mut msg_with_args = msg.arg("ty", self.ty); + if let Some(ty) = self.inner_ty { + msg_with_args = msg_with_args.arg("inner_ty", ty); + } + msg_with_args.format() + }; + + if let Some(help) = self.help { + diag.help(add_args(help)); + } + + diag.note(add_args(self.note)); + if let Some(note) = self.span_note { + diag.span_note(note, msg!("the type is defined here")); + }; + } +} + 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 @@ -2365,16 +2392,12 @@ impl<'a> Diagnostic<'a, ()> for ImproperCTypes<'_> { level, msg!("`extern` {$desc} uses type `{$ty}`, which is not FFI-safe"), ) - .with_arg("ty", self.ty) - .with_arg("desc", self.desc) .with_span_label(self.label, msg!("not FFI-safe")); - if let Some(help) = self.help { - diag.help(help); - } - diag.note(self.note); - if let Some(note) = self.span_note { - diag.span_note(note, msg!("the type is defined here")); + for reason in self.reasons.into_iter() { + diag.subdiagnostic(reason); } + diag.arg("ty", self.ty); + diag.arg("desc", self.desc); diag } } diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 0c34d9da66f1d..43ef10aa4f451 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -20,7 +20,7 @@ use rustc_target::spec::Os; use tracing::debug; use super::repr_nullable_ptr; -use crate::lints::{ImproperCTypes, UsesPowerAlignment}; +use crate::lints::{ImproperCTypes, ImproperCTypesLayer, UsesPowerAlignment}; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { @@ -281,10 +281,184 @@ enum FnPos { Ret, } +#[derive(Clone, Debug)] +struct FfiUnsafeReason<'tcx> { + ty: Ty<'tcx>, + note: DiagMessage, + help: Option, + inner: Option>>, +} + +/// A single explanation (out of possibly multiple) +/// telling why a given element is rendered FFI-unsafe. +/// This goes as deep as the 'core cause', but it might be located elsewhere, possibly in a different crate. +/// So, we also track the 'smallest' type in the explanation that appears in the span of the unsafe element. +/// (we call this the 'cause' or the 'local cause' of the unsafety) +#[derive(Clone, Debug)] +struct FfiUnsafeExplanation<'tcx> { + /// A stack of incrementally "smaller" types, justifications and help messages, + /// ending with the 'core reason' why something is FFI-unsafe, making everything around it also unsafe. + reason: Box>, + /// Override the type considered the local cause of the FFI-unsafety. + /// (e.g.: even if the lint goes into detail as to why a struct used as a function argument + /// is unsafe, have the first lint line say that the fault lies in the use of said struct.) + override_cause_ty: Option>, +} + +/// The result describing the safety (or lack thereof) of a given type. +#[derive(Clone, Debug)] enum FfiResult<'tcx> { + /// The type is known to be safe. FfiSafe, + /// The type is only a phantom annotation. + /// (Safe in some contexts, unsafe in others.) FfiPhantom(Ty<'tcx>), - FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option }, + /// The type is not safe. + /// there might be any number of "explanations" as to why, + /// each being a stack of "reasons" going from the type + /// to a core cause of FFI-unsafety. + FfiUnsafe(Vec>), +} + +impl<'tcx> FfiResult<'tcx> { + /// Simplified creation of the FfiUnsafe variant for a single unsafety reason. + fn new_with_reason(ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { + Self::FfiUnsafe(vec![FfiUnsafeExplanation { + override_cause_ty: None, + reason: Box::new(FfiUnsafeReason { ty, help, note, inner: None }), + }]) + } + + /// If the FfiUnsafe variant, 'wraps' all reasons, + /// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields. + /// Otherwise, keep unchanged. + #[expect(unused)] + fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { + match self { + Self::FfiUnsafe(this) => { + let unsafeties = this + .into_iter() + .map(|FfiUnsafeExplanation { reason, override_cause_ty }| { + let reason = Box::new(FfiUnsafeReason { + ty, + help: help.clone(), + note: note.clone(), + inner: Some(reason), + }); + FfiUnsafeExplanation { reason, override_cause_ty } + }) + .collect::>(); + Self::FfiUnsafe(unsafeties) + } + r @ _ => r, + } + } + /// If the FfiPhantom variant, turns it into a FfiUnsafe version. + /// Otherwise, keep unchanged. + #[expect(unused)] + fn forbid_phantom(self) -> Self { + match self { + Self::FfiPhantom(ty) => { + Self::new_with_reason(ty, msg!("composed only of `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 @@ -330,6 +504,8 @@ enum OuterTyKind { /// A variant that should not exist, /// but is needed because we don't change the lint's behavior yet NoneThroughFnPtr, + /// For struct/enum/union fields + AdtField, /// Placeholder for properties that will be used eventually Other, } @@ -339,12 +515,16 @@ impl OuterTyKind { fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { ty::FnPtr(..) => Self::NoneThroughFnPtr, - ty::RawPtr(..) - | ty::Ref(..) - | ty::Adt(..) - | ty::Tuple(..) - | ty::Array(..) - | ty::Slice(_) => OuterTyKind::Other, + ty::Adt(..) => { + if ty.boxed_ty().is_some() { + Self::Other + } else { + Self::AdtField + } + } + ty::RawPtr(..) | ty::Ref(..) | ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => { + Self::Other + } _ => bug!("Unexpected outer type {ty:?}"), } } @@ -463,6 +643,11 @@ impl VisitorState { // rust-defined functions, as well as FnPtrs self.root_use_flags.contains(RootUseFlags::THEORETICAL) || self.is_in_defined_function() } + + /// Whether the current type is an ADT field + fn is_field(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::AdtField) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -521,11 +706,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: msg!("box cannot be represented as a single pointer"), - help: None, - }; + msg!("box cannot be represented as a single pointer"), + None, + ); } } else { // (mid-retcon-commit-chain comment:) @@ -584,12 +769,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Transparent newtypes have at most one non-ZST field which needs to be checked.. let field_ty = maybe_normalize_erasing_regions(self.cx, field.ty(self.cx.tcx, args)); - match self.visit_type(state.next(ty), field_ty) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - - false + return self.visit_type(state.next(ty), field_ty); } else { // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all // `PhantomData`). @@ -605,8 +785,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let field_ty = maybe_normalize_erasing_regions(self.cx, field.ty(self.cx.tcx, args)); all_phantom &= match self.visit_type(state.next(ty), field_ty) { FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } if ty.is_unit() => false, FfiPhantom(..) => true, r @ FfiUnsafe { .. } => return r, } @@ -615,11 +793,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if all_phantom { FfiPhantom(ty) } else if transparent_with_all_zst_fields { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: msg!("this struct contains only zero-sized fields"), - help: None, - } + msg!("this struct contains only zero-sized fields"), + None, + ) } else { FfiSafe } @@ -633,17 +811,16 @@ 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() { msg!("this struct has unspecified layout") } else { msg!("this union has unspecified layout") }, - help: if def.is_struct() { + if def.is_struct() { Some(msg!( "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct" )) @@ -653,35 +830,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union" )) }, - }; + ); } 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() { msg!("this struct is non-exhaustive") } else { msg!("this union is non-exhaustive") }, - help: None, - }; + None, + ); } if def.non_enum_variant().fields.is_empty() { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { msg!("this struct has no fields") } else { msg!("this union has no fields") }, - help: if def.is_struct() { + if def.is_struct() { Some(msg!("consider adding a member to this struct")) } else { Some(msg!("consider adding a member to this union")) }, - } + ) } else { self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) } @@ -709,20 +886,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return self.visit_type(state.next(ty), inner_ty); } - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: msg!("enum has no representation hint"), - help: Some(msg!( + msg!("enum has no representation hint"), + Some(msg!( "consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum" )), - }; + ); } + // 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(()), @@ -764,13 +944,13 @@ 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: msg!("`CStr`/`CString` do not have a guaranteed layout"), - help: Some(msg!( + msg!("`CStr`/`CString` do not have a guaranteed layout"), + Some(msg!( "consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`" )), - }; + ); } self.visit_struct_or_union(state, ty, def, args) } @@ -790,44 +970,48 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Bool => FfiResult::FfiSafe, - ty::Char => FfiResult::FfiUnsafe { + ty::Char => FfiResult::new_with_reason( ty, - reason: msg!("the `char` type has no C equivalent"), - help: Some(msg!("consider using `u32` or `libc::wchar_t` instead")), - }, + msg!("the `char` type has no C equivalent"), + Some(msg!("consider using `u32` or `libc::wchar_t` instead")), + ), - ty::Slice(_) => FfiUnsafe { + ty::Slice(_) => FfiResult::new_with_reason( ty, - reason: msg!("slices have no C equivalent"), - help: Some(msg!("consider using a raw pointer instead")), - }, + msg!("slices have no C equivalent"), + Some(msg!("consider using a raw pointer instead")), + ), ty::Dynamic(..) => { - FfiUnsafe { ty, reason: msg!("trait objects have no C equivalent"), help: None } + FfiResult::new_with_reason(ty, msg!("trait objects have no C equivalent"), None) } - ty::Str => FfiUnsafe { + ty::Str => FfiResult::new_with_reason( ty, - reason: msg!("string slices have no C equivalent"), - help: Some(msg!("consider using `*const u8` and a length instead")), - }, + msg!("string slices have no C equivalent"), + Some(msg!("consider using `*const u8` and a length instead")), + ), ty::Tuple(tuple) => { if tuple.is_empty() - && state.is_in_function_return() - && matches!( - state.outer_ty_kind, - OuterTyKind::None | OuterTyKind::NoneThroughFnPtr - ) + && (( + state.is_in_function_return() + // C functions can return void + && matches!( + state.outer_ty_kind, + OuterTyKind::None | OuterTyKind::NoneThroughFnPtr + ) + ) + // `()` fields are safe + || state.is_field()) { - // C functions can return void FfiSafe } else { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: msg!("tuples have unspecified layout"), - help: Some(msg!("consider using a struct instead")), - } + msg!("tuples have unspecified layout"), + Some(msg!("consider using a struct instead")), + ) } } @@ -854,11 +1038,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: msg!("passing raw arrays by value is not FFI-safe"), - help: Some(msg!("consider passing a pointer to the array")), - } + msg!("passing raw arrays by value is not FFI-safe"), + Some(msg!("consider passing a pointer to the array")), + ) } else { // let's allow phantoms to go through, // since an array of 1-ZSTs is also a 1-ZST @@ -869,13 +1053,13 @@ 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: msg!("this function pointer has Rust-specific calling convention"), - help: Some(msg!( + msg!("this function pointer has Rust-specific calling convention"), + Some(msg!( "consider using an `extern fn(...) -> ...` function pointer instead" )), - }; + ); } let sig = tcx.instantiate_bound_regions_with_erased(sig); @@ -897,7 +1081,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::AliasTy { kind: ty::Opaque { .. }, .. }) => { - FfiUnsafe { ty, reason: msg!("opaque types have no C equivalent"), help: None } + FfiResult::new_with_reason(ty, msg!("opaque types have no C equivalent"), None) } // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, @@ -909,11 +1093,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::UnsafeBinder(_) => FfiUnsafe { + ty::UnsafeBinder(_) => FfiResult::new_with_reason( ty, - reason: msg!("unsafe binders are incompatible with foreign function interfaces"), - help: None, - }, + msg!("unsafe binders are incompatible with foreign function interfaces"), + None, + ), // Safety net for when normalization reveals a body's own defining opaque // (e.g. `async extern fn`'s `impl Future` → `Coroutine`); the nicer @@ -922,11 +1106,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Closure(..) | ty::CoroutineClosure(..) | ty::Coroutine(..) - | ty::CoroutineWitness(..) => FfiUnsafe { + | ty::CoroutineWitness(..) => FfiResult::new_with_reason( ty, - reason: msg!("closures and coroutines are not FFI-safe"), - help: None, - }, + msg!("closures and coroutines are not FFI-safe"), + None, + ), ty::Param(..) | ty::Alias( @@ -962,10 +1146,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - ty.visit_with(&mut ProhibitOpaqueTypes).break_value().map(|ty| FfiResult::FfiUnsafe { - ty, - reason: msg!("opaque types have no C equivalent"), - help: None, + ty.visit_with(&mut ProhibitOpaqueTypes).break_value().map(|ty| { + FfiResult::new_with_reason(ty, msg!("opaque types have no C equivalent"), None) }) } @@ -1148,15 +1330,53 @@ impl<'tcx> ImproperCTypesLint { FfiResult::FfiPhantom(ty) => { self.emit_ffi_unsafe_type_lint( cx, - ty, + ty.clone(), sp, - msg!("composed only of `PhantomData`"), - None, + vec![ImproperCTypesLayer { + ty, + note: msg!("composed only of `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); + } } } } @@ -1166,8 +1386,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 { @@ -1178,14 +1397,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 5102ce45f6d196d22c093adce2a5acd40834000c Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 00:35:22 +0200 Subject: [PATCH 02/17] ImproperCTypes: Redo the improper_ctypes / ..._definitions separation Externally, all lint messages concerning argument/return types of function pointers ("callbacks") have been properly assigned to the `improper_ctypes` lint, in terms of the ability to allow/deny/etc FFI-unsafety. The lint messages for those callback-related unsafeties has also changed. The documentation of the lints has also been updated. Internally, there is also a clean separation between callback-related lint messages and other messages, though this separation vanishes when hooking into the linting system as a whole. a new lint group, `improper_c_boundaries`, has also been added. --- compiler/rustc_lint/src/lib.rs | 2 + compiler/rustc_lint/src/lints.rs | 9 +- compiler/rustc_lint/src/types.rs | 4 +- .../rustc_lint/src/types/improper_ctypes.rs | 99 ++++++++++++------- src/tools/lint-docs/src/groups.rs | 1 + tests/assembly-llvm/naked-functions/wasm32.rs | 1 - tests/ui/abi/compatibility.rs | 3 +- tests/ui/abi/extern/extern-pass-empty.rs | 3 +- tests/ui/abi/foreign/foreign-fn-with-byval.rs | 2 +- ...sized-args-in-c-abi-issues-94223-115845.rs | 2 +- tests/ui/asm/naked-functions-ffi.stderr | 2 +- .../cmse-nonsecure-call/return-via-stack.rs | 2 +- .../cmse-nonsecure-call/via-registers.rs | 3 +- .../ui/collections/hashmap/hashmap-memory.rs | 2 +- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 2 +- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 5 +- tests/ui/extern/extern-C-str-arg-ice-80125.rs | 2 +- .../extern/extern-C-str-arg-ice-80125.stderr | 5 +- tests/ui/issues/issue-51907.rs | 2 + tests/ui/lint/clashing-extern-fn.stderr | 2 +- tests/ui/lint/extern-C-fnptr-lints-slices.rs | 4 +- .../lint/extern-C-fnptr-lints-slices.stderr | 6 +- tests/ui/lint/improper-ctypes/lint-94223.rs | 26 ++--- .../ui/lint/improper-ctypes/lint-94223.stderr | 30 +++--- tests/ui/lint/improper-ctypes/lint-fn.rs | 2 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 2 +- .../lint/improper-ctypes/mustpass-113436.rs | 2 +- .../improper-ctypes/mustpass-134060.stderr | 2 +- tests/ui/lint/lint-gpu-kernel.amdgpu.stderr | 2 +- tests/ui/lint/lint-gpu-kernel.nvptx.stderr | 2 +- .../repr/repr-transparent-issue-87496.stderr | 2 +- 31 files changed, 136 insertions(+), 97 deletions(-) diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 5271217593e62..0f61482999954 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -384,6 +384,8 @@ fn register_builtins(store: &mut LintStore) { REFINING_IMPL_TRAIT_INTERNAL ); + add_lint_group!("improper_c_boundaries", IMPROPER_CTYPES_DEFINITIONS, IMPROPER_CTYPES); + add_lint_group!("deprecated_safe", DEPRECATED_SAFE_2024); add_lint_group!( diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 889cba5c9831d..c5aa293e44122 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2387,12 +2387,9 @@ pub(crate) struct ImproperCTypes<'a> { // Used because of the complexity of Option, DiagMessage, and Option impl<'a> Diagnostic<'a, ()> for ImproperCTypes<'_> { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { - let mut diag = Diag::new( - dcx, - level, - msg!("`extern` {$desc} uses type `{$ty}`, which is not FFI-safe"), - ) - .with_span_label(self.label, msg!("not FFI-safe")); + let mut diag = + Diag::new(dcx, level, msg!("{$desc} uses type `{$ty}`, which is not FFI-safe")) + .with_span_label(self.label, msg!("not FFI-safe")); for reason in self.reasons.into_iter() { diag.subdiagnostic(reason); } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 083d2e4ee0499..27642a5c3b212 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -12,7 +12,9 @@ use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::debug; mod improper_ctypes; // these files do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations -pub(crate) use improper_ctypes::ImproperCTypesLint; +pub(crate) use improper_ctypes::{ + IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, ImproperCTypesLint, +}; use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 43ef10aa4f451..fbc6b76164af5 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -26,13 +26,19 @@ use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { /// The `improper_ctypes` lint detects incorrect use of types in foreign /// modules. + /// (In other words, declarations of items defined in foreign code.) + /// This also includes all [`extern` function] pointers. + /// + /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier /// /// ### Example /// /// ```rust /// unsafe extern "C" { /// static STATIC: String; + /// fn some_func(a:String); /// } + /// extern "C" fn register_callback(a: i32, call: extern "C" fn(char)) { /* ... */ } /// ``` /// /// {{produces}} @@ -45,7 +51,7 @@ declare_lint! { /// detects a probable mistake in a definition. The lint usually should /// provide a description of the issue, along with possibly a hint on how /// to resolve it. - IMPROPER_CTYPES, + pub(crate) IMPROPER_CTYPES, Warn, "proper use of libc types in foreign modules" } @@ -53,6 +59,7 @@ declare_lint! { declare_lint! { /// The `improper_ctypes_definitions` lint detects incorrect use of /// [`extern` function] definitions. + /// (In other words, functions to be used by foreign code.) /// /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier /// @@ -72,7 +79,7 @@ declare_lint! { /// lint is an alert that these types should not be used. The lint usually /// should provide a description of the issue, along with possibly a hint /// on how to resolve it. - IMPROPER_CTYPES_DEFINITIONS, + pub(crate) IMPROPER_CTYPES_DEFINITIONS, Warn, "proper use of libc types in foreign item definitions" } @@ -135,7 +142,7 @@ declare_lint! { declare_lint_pass!(ImproperCTypesLint => [ IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, - USES_POWER_ALIGNMENT + USES_POWER_ALIGNMENT, ]); /// A common pattern in this lint is to attempt normalize_erasing_regions, @@ -265,13 +272,19 @@ fn check_struct_for_power_alignment<'tcx>( } } -/// Annotates whether we are in the context of an item *defined* in rust -/// and exposed to an FFI boundary, -/// or the context of an item from elsewhere, whose interface is re-*declared* in rust. -#[derive(Clone, Copy)] +/// Annotates the nature of the "original item" being checked, and its relation +/// to FFI boundaries. +/// Mainly, whether is is something defined in rust and exported through the FFI boundary, +/// or something rust imports through the same boundary. +/// Callbacks are ultimately treated as imported items, in terms of denying/warning/ignoring FFI-unsafety +#[derive(Clone, Copy, Debug)] enum CItemKind { - Declaration, - Definition, + /// Imported items in an `extern "C"` block (function declarations, static variables) -> IMPROPER_CTYPES + ImportedExtern, + /// `extern "C"` function definitions, to be used elsewhere -> IMPROPER_CTYPES_DEFINITIONS, + ExportedFunction, + /// `extern "C"` function pointers -> also IMPROPER_CTYPES, + Callback, } /// Annotates whether we are in the context of a function's argument types or return type. @@ -589,10 +602,13 @@ impl VisitorState { /// Get the proper visitor state for a given function's arguments or return type. fn fn_entry_point(fn_mode: CItemKind, fn_pos: FnPos) -> Self { let p_flags = match (fn_mode, fn_pos) { - (CItemKind::Definition, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, - (CItemKind::Declaration, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, - (CItemKind::Definition, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, - (CItemKind::Declaration, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + (CItemKind::ExportedFunction, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, + (CItemKind::ImportedExtern, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, + (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + // we could also deal with CItemKind::Callback, + // but we bake an assumption from this function's call sites here. + _ => bug!("cannot be called with CItemKind::{:?}", fn_mode), }; VisitorState { root_use_flags: p_flags, outer_ty_kind: OuterTyKind::None, depth: 0 } } @@ -701,7 +717,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; @@ -1239,7 +1256,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); } } @@ -1285,9 +1302,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_entry_point(), 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". @@ -1390,12 +1407,16 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, ) { let lint = match fn_mode { - CItemKind::Declaration => IMPROPER_CTYPES, - CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + CItemKind::ImportedExtern => IMPROPER_CTYPES, + CItemKind::ExportedFunction => IMPROPER_CTYPES_DEFINITIONS, + // Internally, we treat this differently, but at the end of the day + // their linting needs to be enabled/disabled alongside that of "FFI-imported" items. + CItemKind::Callback => IMPROPER_CTYPES, }; let desc = match fn_mode { - CItemKind::Declaration => "block", - CItemKind::Definition => "fn", + CItemKind::ImportedExtern => "`extern` block", + CItemKind::ExportedFunction => "`extern` fn", + CItemKind::Callback => "`extern` callback", }; for reason in reasons.iter_mut() { reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() @@ -1411,13 +1432,20 @@ impl<'tcx> ImproperCTypesLint { } } -/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in -/// `extern "C" { }` blocks): +/// IMPROPER_CTYPES checks items that are part of a header to a non-rust library +/// Namely, functions and static variables in `extern "" { }`, +/// if `` is external (e.g. "C"). +/// it also checks for function pointers marked with an external ABI. +/// (fields of type `extern "" fn`, where e.g. `` is `C`) +/// These pointers are searched in all other items which contain types +/// (e.g.functions, struct definitions, etc) /// -/// - `extern "" fn` definitions are checked in the same way as the -/// `ImproperCtypesDeclarations` visitor checks functions if `` is external (e.g. "C"). -/// - All other items which contain types (e.g. other functions, struct definitions, etc) are -/// checked for extern fn-ptrs with external ABIs. +/// `IMPROPER_CTYPES_DEFINITIONS` checks rust-defined functions that are marked +/// to be used from the other side of a FFI boundary. +/// In other words, `extern "" fn` definitions and trait-method declarations. +/// This only matters if `` is external (e.g. `C`). +/// +/// maybe later: specialised lints for pointees impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { let abi = cx.tcx.hir_get_foreign_abi(it.hir_id()); @@ -1428,11 +1456,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, ); @@ -1455,7 +1488,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { VisitorState::static_entry_point(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(), - 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 @@ -1489,7 +1522,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { VisitorState::static_entry_point(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity().skip_norm_wip(), - CItemKind::Definition, + CItemKind::ImportedExtern, ); } @@ -1514,9 +1547,9 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::Definition, id, decl); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, id, decl); } else { - self.check_fn_for_external_abi_fnptr(cx, CItemKind::Definition, id, decl); + self.check_fn_for_external_abi_fnptr(cx, CItemKind::ExportedFunction, id, decl); } } } diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs index a24fbbc0ceab3..10ae9e6421b19 100644 --- a/src/tools/lint-docs/src/groups.rs +++ b/src/tools/lint-docs/src/groups.rs @@ -30,6 +30,7 @@ static GROUP_DESCRIPTIONS: &[(&str, &str)] = &[ "unknown-or-malformed-diagnostic-attributes", "detects unknown or malformed diagnostic attributes", ), + ("improper-c-boundaries", "Lints for points where rust code interacts with non-rust code"), ]; type LintGroups = BTreeMap>; diff --git a/tests/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index e2a2ab94c8a33..b2754029021bf 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -99,7 +99,6 @@ extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm32-unknown: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () -#[allow(improper_ctypes_definitions)] #[no_mangle] #[unsafe(naked)] extern "C" fn fn_i128_i128(num: i128) -> i128 { diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index 6071ad9bb435b..75a9eb9906b88 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -74,7 +74,8 @@ #![feature(no_core, rustc_attrs, lang_items)] #![feature(unsized_fn_params, transparent_unions)] #![no_core] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] +#![allow(improper_ctypes_definitions, improper_ctypes)] // FIXME: some targets are broken in various ways. // Hence there are `cfg` throughout this test to disable parts of it on those targets. diff --git a/tests/ui/abi/extern/extern-pass-empty.rs b/tests/ui/abi/extern/extern-pass-empty.rs index 1ad52b128ad93..f38f76166bf27 100644 --- a/tests/ui/abi/extern/extern-pass-empty.rs +++ b/tests/ui/abi/extern/extern-pass-empty.rs @@ -1,5 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. +#![allow(improper_ctypes)] +// FIXME: this test is inherently not FFI-safe. // Test a foreign function that accepts empty struct. diff --git a/tests/ui/abi/foreign/foreign-fn-with-byval.rs b/tests/ui/abi/foreign/foreign-fn-with-byval.rs index 9908ec2d2c01a..dbf80385e15f7 100644 --- a/tests/ui/abi/foreign/foreign-fn-with-byval.rs +++ b/tests/ui/abi/foreign/foreign-fn-with-byval.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(improper_ctypes, improper_ctypes_definitions)] +#![allow(improper_ctypes)] #[derive(Copy, Clone)] pub struct S { diff --git a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs index 7d21307e1b2d9..0dfe91b95dd68 100644 --- a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs +++ b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes_definitions, improper_ctypes)] #![feature(unsized_fn_params)] #![crate_type = "lib"] diff --git a/tests/ui/asm/naked-functions-ffi.stderr b/tests/ui/asm/naked-functions-ffi.stderr index f7893a3b8de98..ed31959e18565 100644 --- a/tests/ui/asm/naked-functions-ffi.stderr +++ b/tests/ui/asm/naked-functions-ffi.stderr @@ -6,7 +6,7 @@ LL | pub extern "C" fn naked(p: char) -> u128 { | = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs index a8c69216e2048..c4b31b3dce489 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 @@ -36,7 +36,7 @@ pub fn test( ) { } -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] struct Test { u128: extern "cmse-nonsecure-call" fn() -> u128, //~ ERROR [E0798] i128: extern "cmse-nonsecure-call" fn() -> i128, //~ ERROR [E0798] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs index 5528865fc840e..fe3bcf9c723dd 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs @@ -26,7 +26,7 @@ pub enum ReprTransparentEnumU64 { pub struct U32Compound(u16, u16); #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] pub fn params( f1: extern "cmse-nonsecure-call" fn(), f2: extern "cmse-nonsecure-call" fn(u32, u32, u32, u32), @@ -39,6 +39,7 @@ pub fn params( } #[no_mangle] +#[allow(improper_ctypes)] pub fn returns( f1: extern "cmse-nonsecure-call" fn() -> u32, f2: extern "cmse-nonsecure-call" fn() -> u64, diff --git a/tests/ui/collections/hashmap/hashmap-memory.rs b/tests/ui/collections/hashmap/hashmap-memory.rs index 961ebcc0720dc..d26dba7005519 100644 --- a/tests/ui/collections/hashmap/hashmap-memory.rs +++ b/tests/ui/collections/hashmap/hashmap-memory.rs @@ -1,7 +1,7 @@ //@ edition:2015 //@ run-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes)] #![allow(non_camel_case_types)] #![allow(dead_code)] #![allow(unused_mut)] diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index 084c5ba73fcbc..b08a2a4f666b1 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 @@ -7,7 +7,7 @@ #![allow(function_casts_as_integer)] 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 3b98f9a55f8ed..5d595e20dea4c 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:9:12 | LL | type Foo = extern "C" fn(::std::ffi::CStr); @@ -6,7 +6,7 @@ LL | type Foo = extern "C" fn(::std::ffi::CStr); | = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` block uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:12:18 @@ -16,7 +16,6 @@ LL | fn meh(blah: Foo); | = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout - = note: `#[warn(improper_ctypes)]` on by default warning: 2 warnings emitted diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 0908d6199efb8..571652b87369d 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -1,7 +1,7 @@ // issue: rust-lang/rust#80125 //@ check-pass type ExternCallback = extern "C" fn(*const u8, u32, str); -//~^ WARN `extern` fn uses type `str`, which is not FFI-safe +//~^ WARN `extern` callback uses type `str`, which is not FFI-safe pub struct Struct(ExternCallback); diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index ebd6cec6ecd3f..372d4ba2e1937 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -1,4 +1,4 @@ -warning: `extern` fn uses type `str`, which is not FFI-safe +warning: `extern` callback uses type `str`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:3:23 | LL | type ExternCallback = extern "C" fn(*const u8, u32, str); @@ -6,7 +6,7 @@ LL | type ExternCallback = extern "C" fn(*const u8, u32, str); | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` fn uses type `str`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:44 @@ -16,6 +16,7 @@ LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` fn uses type `Struct`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:63 diff --git a/tests/ui/issues/issue-51907.rs b/tests/ui/issues/issue-51907.rs index bf3f629df4970..808064fa02240 100644 --- a/tests/ui/issues/issue-51907.rs +++ b/tests/ui/issues/issue-51907.rs @@ -1,6 +1,8 @@ //@ run-pass trait Foo { + #[allow(improper_ctypes_definitions)] extern "C" fn borrow(&self); + #[allow(improper_ctypes_definitions)] extern "C" fn take(self: Box); } diff --git a/tests/ui/lint/clashing-extern-fn.stderr b/tests/ui/lint/clashing-extern-fn.stderr index 0c27547a6ed8f..e09ac4f71fbc8 100644 --- a/tests/ui/lint/clashing-extern-fn.stderr +++ b/tests/ui/lint/clashing-extern-fn.stderr @@ -6,7 +6,7 @@ LL | fn hidden_niche_transparent_no_niche() -> Option>>`, which is not FFI-safe --> $DIR/clashing-extern-fn.rs:487:46 diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 0c35eb37a4890..08db0539ab4e7 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.rs +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.rs @@ -1,9 +1,9 @@ -#[deny(improper_ctypes_definitions)] +#[deny(improper_ctypes)] // It's an improper ctype (a slice) arg in an extern "C" fnptr. pub type F = extern "C" fn(&[u8]); -//~^ ERROR: `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR: `extern` callback uses type `[u8]`, which is not FFI-safe fn main() {} diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index d13f93ca96f22..2ac80150d7b15 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,4 +1,4 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 | LL | pub type F = extern "C" fn(&[u8]); @@ -9,8 +9,8 @@ LL | pub type F = extern "C" fn(&[u8]); note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 | -LL | #[deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/lint-94223.rs index ac24f61b0ac7a..0c8d531f69247 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/lint-94223.rs @@ -1,35 +1,35 @@ #![crate_type = "lib"] -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] pub fn bad(f: extern "C" fn([u8])) {} -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe pub fn bad_twice(f: Result) {} -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe -//~^^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe +//~^^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe struct BadStruct(extern "C" fn([u8])); -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe enum BadEnum { A(extern "C" fn([u8])), - //~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe } enum BadUnion { A(extern "C" fn([u8])), - //~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe } type Foo = extern "C" fn([u8]); -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe pub trait FooTrait { type FooType; } pub type Foo2 = extern "C" fn(Option<&::FooType>); -//~^ ERROR `extern` fn uses type `Option<&::FooType>`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe pub struct FfiUnsafe; @@ -39,11 +39,11 @@ extern "C" fn f(_: FfiUnsafe) { } pub static BAD: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe pub static BAD_TWICE: Result = Ok(f); -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe -//~^^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe +//~^^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index 008debf8f010a..db05f545f106e 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -1,4 +1,4 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:4:15 | LL | pub fn bad(f: extern "C" fn([u8])) {} @@ -7,12 +7,12 @@ LL | pub fn bad(f: extern "C" fn([u8])) {} = help: consider using a raw pointer instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-94223.rs:2:9 + --> $DIR/lint-94223.rs:2:38 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes_definitions, improper_ctypes)] + | ^^^^^^^^^^^^^^^ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:7:28 | LL | pub fn bad_twice(f: Result) {} @@ -21,7 +21,7 @@ LL | pub fn bad_twice(f: Result) {} = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:7:49 | LL | pub fn bad_twice(f: Result) {} @@ -30,7 +30,7 @@ LL | pub fn bad_twice(f: Result) {} = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:11:18 | LL | struct BadStruct(extern "C" fn([u8])); @@ -39,7 +39,7 @@ LL | struct BadStruct(extern "C" fn([u8])); = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:15:7 | LL | A(extern "C" fn([u8])), @@ -48,7 +48,7 @@ LL | A(extern "C" fn([u8])), = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:20:7 | LL | A(extern "C" fn([u8])), @@ -57,7 +57,7 @@ LL | A(extern "C" fn([u8])), = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:24:12 | LL | type Foo = extern "C" fn([u8]); @@ -66,7 +66,7 @@ LL | type Foo = extern "C" fn([u8]); = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `Option<&::FooType>`, which is not FFI-safe +error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe --> $DIR/lint-94223.rs:31:20 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); @@ -75,7 +75,7 @@ LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:41:17 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; @@ -89,7 +89,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:44:30 | LL | pub static BAD_TWICE: Result = Ok(f); @@ -103,7 +103,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:44:56 | LL | pub static BAD_TWICE: Result = Ok(f); @@ -117,7 +117,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:48:22 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 5abefab46dfaa..dfcdf87ae8e2b 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -1,5 +1,5 @@ #![allow(private_interfaces)] -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] use std::default::Default; use std::marker::PhantomData; diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 00258e929e179..c6ca382e435a1 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -9,7 +9,7 @@ LL | pub extern "C" fn slice_type(p: &[u32]) { } note: the lint level is defined here --> $DIR/lint-fn.rs:2:9 | -LL | #![deny(improper_ctypes_definitions)] +LL | #![deny(improper_ctypes_definitions, improper_ctypes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `str`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/mustpass-113436.rs index d5acdc45f92e5..83afaa24d2ef9 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113436.rs +++ b/tests/ui/lint/improper-ctypes/mustpass-113436.rs @@ -1,5 +1,5 @@ //@ check-pass -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] #[repr(C)] pub struct Wrap(T); diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr b/tests/ui/lint/improper-ctypes/mustpass-134060.stderr index 791b2f7370983..9b2de49a7eb51 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr +++ b/tests/ui/lint/improper-ctypes/mustpass-134060.stderr @@ -6,7 +6,7 @@ LL | extern "C" fn foo_(&self, _: ()) -> i64 { | = help: consider using a struct instead = note: tuples have unspecified layout - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr b/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr index 21eebe42f8b1d..a6ccfd2980cdf 100644 --- a/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr +++ b/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr @@ -6,7 +6,7 @@ LL | extern "gpu-kernel" fn arg_zst(_: ()) { } | = help: consider using a struct instead = note: tuples have unspecified layout - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: passing type `()` to a function with "gpu-kernel" ABI may have unexpected behavior --> $DIR/lint-gpu-kernel.rs:36:35 diff --git a/tests/ui/lint/lint-gpu-kernel.nvptx.stderr b/tests/ui/lint/lint-gpu-kernel.nvptx.stderr index 21eebe42f8b1d..a6ccfd2980cdf 100644 --- a/tests/ui/lint/lint-gpu-kernel.nvptx.stderr +++ b/tests/ui/lint/lint-gpu-kernel.nvptx.stderr @@ -6,7 +6,7 @@ LL | extern "gpu-kernel" fn arg_zst(_: ()) { } | = help: consider using a struct instead = note: tuples have unspecified layout - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: passing type `()` to a function with "gpu-kernel" ABI may have unexpected behavior --> $DIR/lint-gpu-kernel.rs:36:35 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 d18f356640a43ccad41b5f013a8e1abdfb4ab67d Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 27 Aug 2025 21:13:09 +0200 Subject: [PATCH 03/17] 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. --- .../rustc_lint/src/types/improper_ctypes.rs | 185 +++++++----------- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 1 - ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 15 +- tests/ui/extern/extern-C-str-arg-ice-80125.rs | 3 +- .../extern/extern-C-str-arg-ice-80125.stderr | 17 +- .../lint/extern-C-fnptr-lints-slices.stderr | 4 +- .../ui/lint/improper-ctypes/lint-94223.stderr | 48 ++--- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 4 +- .../lint/improper-ctypes/lint-ctypes.stderr | 24 +-- tests/ui/lint/improper-ctypes/lint-fn.rs | 11 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 18 +- 11 files changed, 129 insertions(+), 201 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index fbc6b76164af5..9707d843f024c 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, Unnormalized, + self, Adt, AdtDef, AdtKind, Binder, FnSig, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, Unnormalized, }; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::def_id::LocalDefId; @@ -145,6 +145,27 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT, ]); +type Sig<'tcx> = Binder<'tcx, FnSig<'tcx>>; + +/// Extract (binder-wrapped) FnSig object from a FnPtr's mir::Ty +fn get_sig_from_fnptr_ty<'tcx>(ty: Ty<'tcx>) -> Sig<'tcx> { + match *ty.kind() { + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(hdr); + if sig.abi().is_rustic_abi() { + bug!( + "expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one" + ) + } else { + sig + } + } + r @ _ => { + bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not {:?}", r,) + } + } +} + /// A common pattern in this lint is to attempt normalize_erasing_regions, /// but keep the original type if it were to fail. /// This may or may not be supported in the logic behind the `Unnormalized` wrapper, @@ -514,9 +535,6 @@ bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum OuterTyKind { None, - /// A variant that should not exist, - /// but is needed because we don't change the lint's behavior yet - NoneThroughFnPtr, /// For struct/enum/union fields AdtField, /// Placeholder for properties that will be used eventually @@ -527,7 +545,7 @@ impl OuterTyKind { /// Computes the relationship by providing the containing Ty itself fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { - ty::FnPtr(..) => Self::NoneThroughFnPtr, + ty::FnPtr(..) => Self::None, ty::Adt(..) => { if ty.boxed_ty().is_some() { Self::Other @@ -584,31 +602,15 @@ impl VisitorState { } } - /// From an existing state, compute the state of any subtype of the current type. - /// (Case where the current type is a function pointer, - /// meaning we need to specify if the subtype is an argument or the return.) - fn next_in_fnptr(&self, current_ty: Ty<'_>, fn_pos: FnPos) -> Self { - assert!(matches!(current_ty.kind(), ty::FnPtr(..))); - VisitorState { - root_use_flags: match fn_pos { - FnPos::Ret => RootUseFlags::RETURN_TY_IN_FNPTR, - FnPos::Arg => RootUseFlags::ARGUMENT_TY_IN_FNPTR, - }, - outer_ty_kind: OuterTyKind::from_ty(current_ty), - depth: self.depth + 1, - } - } - /// Get the proper visitor state for a given function's arguments or return type. fn fn_entry_point(fn_mode: CItemKind, fn_pos: FnPos) -> Self { let p_flags = match (fn_mode, fn_pos) { (CItemKind::ExportedFunction, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::Callback, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_FNPTR, (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, - // we could also deal with CItemKind::Callback, - // but we bake an assumption from this function's call sites here. - _ => bug!("cannot be called with CItemKind::{:?}", fn_mode), + (CItemKind::Callback, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_FNPTR, }; VisitorState { root_use_flags: p_flags, outer_ty_kind: OuterTyKind::None, depth: 0 } } @@ -677,15 +679,10 @@ struct ImproperCTypesVisitor<'a, 'tcx> { /// The original type being checked, before we recursed /// to any other types it contains. base_ty: Ty<'tcx>, - base_fn_mode: CItemKind, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new( - cx: &'a LateContext<'tcx>, - base_ty: Unnormalized<'tcx, Ty<'tcx>>, - base_fn_mode: CItemKind, - ) -> Self { + fn new(cx: &'a LateContext<'tcx>, base_ty: Unnormalized<'tcx, Ty<'tcx>>) -> Self { // Skip normalization for opaques: even in `TypingMode::Borrowck` the body's own // defining opaques still get revealed, leaving entries in `OpaqueTypeStorage` that // ICE on `InferCtxt` drop (issue #156352). @@ -694,7 +691,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else { maybe_normalize_erasing_regions(cx, base_ty) }; - ImproperCTypesVisitor { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } + ImproperCTypesVisitor { cx, base_ty, cache: FxHashSet::default() } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -712,14 +709,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { IndirectionKind::Box => { // FIXME(ctypes): this logic is broken, but it still fits the current tests: // - for some reason `Box<_>`es in `extern "ABI" {}` blocks - // (including within FnPtr:s) // are not treated as pointers but as FFI-unsafe structs // - otherwise, treat the box itself correctly, and follow pointee safety logic // as described in the other `indirection_type` match branch. - if state.is_in_defined_function() - || (state.is_in_fnptr() - && matches!(self.base_fn_mode, CItemKind::ExportedFunction)) - { + if state.is_in_defined_function() || state.is_in_fnptr() { if inner_ty.is_sized(tcx, self.cx.typing_env()) { return FfiSafe; } else { @@ -1014,10 +1007,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { && (( state.is_in_function_return() // C functions can return void - && matches!( - state.outer_ty_kind, - OuterTyKind::None | OuterTyKind::NoneThroughFnPtr - ) + && matches!(state.outer_ty_kind, OuterTyKind::None) ) // `()` fields are safe || state.is_field()) @@ -1049,10 +1039,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Array(inner_ty, _) => { - if state.is_in_function() - // FIXME(ctypes): VVV-this-VVV shouldn't make a difference between ::None and ::NoneThroughFnPtr - && matches!(state.outer_ty_kind, OuterTyKind::None) - { + if state.is_in_function() && matches!(state.outer_ty_kind, OuterTyKind::None) { // 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( @@ -1067,28 +1054,23 @@ 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, - msg!("this function pointer has Rust-specific calling convention"), + msg!("this function pointer has a Rust-specific calling convention"), Some(msg!( "consider using an `extern fn(...) -> ...` function pointer instead" )), - ); - } - - let sig = tcx.instantiate_bound_regions_with_erased(sig); - for arg in sig.inputs() { - match self.visit_type(state.next_in_fnptr(ty, FnPos::Arg), *arg) { - FfiSafe => {} - r => return r, - } + ) + } else { + FfiSafe } - - let ret_ty = sig.output(); - self.visit_type(state.next_in_fnptr(ty, FnPos::Ret), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1183,7 +1165,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - self.visit_type(state, ty) } } @@ -1194,27 +1175,25 @@ impl<'tcx> ImproperCTypesLint { fn check_type_for_external_abi_fnptr( &mut self, cx: &LateContext<'tcx>, - state: VisitorState, - hir_ty: &hir::Ty<'tcx>, + hir_ty: &'tcx hir::Ty<'tcx>, ty: Ty<'tcx>, - fn_mode: CItemKind, ) { struct FnPtrFinder<'tcx> { current_depth: usize, depths: Vec, - spans: Vec, + decls: Vec<&'tcx hir::FnDecl<'tcx>>, tys: Vec>, } - impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> { - fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { + impl<'tcx> hir::intravisit::Visitor<'tcx> for FnPtrFinder<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, AmbigArg>) { debug!(?ty); self.current_depth += 1; - if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind + if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, decl, .. }) = ty.kind && !abi.is_rustic_abi() { + self.decls.push(*decl); self.depths.push(self.current_depth); - self.spans.push(ty.span); } hir::intravisit::walk_ty(self, ty); @@ -1237,8 +1216,8 @@ impl<'tcx> ImproperCTypesLint { } let mut visitor = FnPtrFinder { - spans: Vec::new(), tys: Vec::new(), + decls: Vec::new(), depths: Vec::new(), current_depth: 0, }; @@ -1247,16 +1226,14 @@ impl<'tcx> ImproperCTypesLint { let all_types = iter::zip( visitor.depths.drain(..), - iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)), + iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)), ); - for (depth, (fn_ptr_ty, span)) in all_types { - let fn_ptr_ty = Unnormalized::new_wip(fn_ptr_ty); - let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode); - let bridge_state = VisitorState { depth, ..state }; - // FIXME(ctypes): make a check_for_fnptr - let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); - - self.process_ffi_result(cx, span, ffi_res, CItemKind::Callback); + for (depth, (fn_ptr_ty, decl)) in all_types { + let sig = get_sig_from_fnptr_ty(fn_ptr_ty); + + // FIXME: does this cause a double normalisation? (since this signature comes from + // the normalised `ty` argument of this method) Is this a performance problem? + self.check_foreign_fn(cx, CItemKind::Callback, Unnormalized::new_wip(sig), decl, depth); } } @@ -1265,7 +1242,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<'_>, ) { @@ -1273,13 +1249,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::fn_entry_point(fn_mode, FnPos::Arg); - self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode); + self.check_type_for_external_abi_fnptr(cx, input_hir, *input_ty); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); - self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); + self.check_type_for_external_abi_fnptr(cx, ret_hir, sig.output()); } } @@ -1300,9 +1274,10 @@ impl<'tcx> ImproperCTypesLint { check_struct_for_power_alignment(cx, item, adt_def); } + /// Check that an extern "ABI" static variable is of a ffi-safe type. fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern); + let mut visitor = ImproperCTypesVisitor::new(cx, ty); let ffi_res = visitor.check_type(VisitorState::static_entry_point(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1312,24 +1287,26 @@ impl<'tcx> ImproperCTypesLint { &mut self, cx: &LateContext<'tcx>, fn_mode: CItemKind, - def_id: LocalDefId, + sig: Unnormalized<'tcx, Sig<'tcx>>, decl: &'tcx hir::FnDecl<'_>, + depth: usize, ) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); - let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); + let sig = cx.tcx.instantiate_bound_regions_with_erased(sig.skip_norm_wip()); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let input_ty = Unnormalized::new_wip(*input_ty); - let state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); - let mut visitor = ImproperCTypesVisitor::new(cx, input_ty, fn_mode); + let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, input_ty); let ffi_res = visitor.check_type(state, input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { let output_ty = Unnormalized::new_wip(sig.output()); - let state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); - let mut visitor = ImproperCTypesVisitor::new(cx, output_ty, fn_mode); + let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, output_ty); let ffi_res = visitor.check_type(state, output_ty); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -1451,24 +1428,14 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { let abi = cx.tcx.hir_get_foreign_abi(it.hir_id()); match it.kind { - hir::ForeignItemKind::Fn(sig, _, _) => { + hir::ForeignItemKind::Fn(hir_sig, _, _) => { // 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, hir_sig.decl); + let sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn( - cx, - CItemKind::ImportedExtern, - it.owner_id.def_id, - sig.decl, - ); - } else { - self.check_fn_for_external_abi_fnptr( - cx, - CItemKind::ImportedExtern, - it.owner_id.def_id, - sig.decl, - ); + self.check_foreign_fn(cx, CItemKind::ImportedExtern, sig, hir_sig.decl, 0); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { @@ -1485,10 +1452,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { | hir::ItemKind::TyAlias(_, _, ty) => { self.check_type_for_external_abi_fnptr( cx, - VisitorState::static_entry_point(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(), - 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 @@ -1519,10 +1484,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { self.check_type_for_external_abi_fnptr( cx, - VisitorState::static_entry_point(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity().skip_norm_wip(), - CItemKind::ImportedExtern, ); } @@ -1546,10 +1509,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // fnptrs are a special case, they always need to be treated as // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline + self.check_fn_for_external_abi_fnptr(cx, id, decl); + let sig = cx.tcx.fn_sig(id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ExportedFunction, id, decl); - } else { - self.check_fn_for_external_abi_fnptr(cx, CItemKind::ExportedFunction, id, decl); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, sig, decl, 0); } } } diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index b08a2a4f666b1..e9aa6898ec766 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 @@ -10,7 +10,6 @@ type Foo = extern "C" fn(::std::ffi::CStr); //~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); - //~^ WARN `extern` block uses type } fn main() { diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr index 5d595e20dea4c..753c8a90ddf24 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -1,21 +1,12 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe - --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:9:12 + --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:9:26 | LL | type Foo = extern "C" fn(::std::ffi::CStr); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^^ not FFI-safe | = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: `extern` block uses type `CStr`, which is not FFI-safe - --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:12:18 - | -LL | fn meh(blah: Foo); - | ^^^ not FFI-safe - | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` - = note: `CStr`/`CString` do not have a guaranteed layout - -warning: 2 warnings emitted +warning: 1 warning emitted diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 571652b87369d..1c1abbe996839 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -7,8 +7,7 @@ pub struct Struct(ExternCallback); #[no_mangle] pub extern "C" fn register_something(bind: ExternCallback) -> Struct { -//~^ WARN `extern` fn uses type `str`, which is not FFI-safe -//~^^ WARN `extern` fn uses type `Struct`, which is not FFI-safe +//~^ WARN `extern` fn uses type `Struct`, which is not FFI-safe Struct(bind) } diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index 372d4ba2e1937..6eded6a78cb74 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -1,23 +1,13 @@ warning: `extern` callback uses type `str`, which is not FFI-safe - --> $DIR/extern-C-str-arg-ice-80125.rs:3:23 + --> $DIR/extern-C-str-arg-ice-80125.rs:3:53 | LL | type ExternCallback = extern "C" fn(*const u8, u32, str); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^ not FFI-safe | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/extern-C-str-arg-ice-80125.rs:9:44 - | -LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { - | ^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default - warning: `extern` fn uses type `Struct`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:63 | @@ -31,6 +21,7 @@ note: the type is defined here | LL | pub struct Struct(ExternCallback); | ^^^^^^^^^^^^^^^^^ + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: 3 warnings emitted +warning: 2 warnings emitted diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index 2ac80150d7b15..6a36ba6c28d6f 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,8 +1,8 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 + --> $DIR/extern-C-fnptr-lints-slices.rs:5:28 | LL | pub type F = extern "C" fn(&[u8]); - | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index db05f545f106e..f079c2705e7cd 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -1,8 +1,8 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:4:15 + --> $DIR/lint-94223.rs:4:29 | LL | pub fn bad(f: extern "C" fn([u8])) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -13,73 +13,73 @@ LL | #![deny(improper_ctypes_definitions, improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:28 + --> $DIR/lint-94223.rs:7:42 | LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:49 + --> $DIR/lint-94223.rs:7:63 | LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:11:18 + --> $DIR/lint-94223.rs:11:32 | LL | struct BadStruct(extern "C" fn([u8])); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:15:7 + --> $DIR/lint-94223.rs:15:21 | LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:20:7 + --> $DIR/lint-94223.rs:20:21 | LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:24:12 + --> $DIR/lint-94223.rs:24:26 | LL | type Foo = extern "C" fn([u8]); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe - --> $DIR/lint-94223.rs:31:20 + --> $DIR/lint-94223.rs:31:34 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:41:17 + --> $DIR/lint-94223.rs:41:31 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -90,10 +90,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:30 + --> $DIR/lint-94223.rs:44:44 | LL | pub static BAD_TWICE: Result = Ok(f); - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -104,10 +104,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:56 + --> $DIR/lint-94223.rs:44:70 | LL | pub static BAD_TWICE: Result = Ok(f); - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -118,10 +118,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:48:22 + --> $DIR/lint-94223.rs:48:36 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 4be4573d2296a..473ac4bb0d7d4 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -61,9 +61,9 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); //~ ERROR: uses type `Box` + pub fn fn_contained(p: RustBadRet); pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: uses type `Box` + pub fn transparent_fn(p: TransparentBadFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` pub fn no_niche_a(a: Option>); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 41453e5c8c98e..e35b1cb5d0101 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -144,7 +144,7 @@ LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe | = help: consider using an `extern fn(...) -> ...` function pointer instead - = note: this function pointer has Rust-specific calling convention + = note: this function pointer has a Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe --> $DIR/lint-ctypes.rs:63:24 @@ -153,16 +153,7 @@ LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe | = 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:64: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 + = note: this function pointer has a Rust-specific calling convention error: `extern` block uses type `str`, which is not FFI-safe --> $DIR/lint-ctypes.rs:65:31 @@ -173,15 +164,6 @@ LL | pub fn transparent_str(p: TransparentStr); = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:66:30 - | -LL | pub fn transparent_fn(p: TransparentBadFn); - | ^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout - error: `extern` block uses type `[u8; 8]`, which is not FFI-safe --> $DIR/lint-ctypes.rs:67:27 | @@ -209,5 +191,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: aborting due to 19 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index dfcdf87ae8e2b..d2cde2f215c56 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -1,5 +1,5 @@ #![allow(private_interfaces)] -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] use std::default::Default; use std::marker::PhantomData; @@ -110,19 +110,22 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } +// ^ FIXME it doesn't see the error... but at least it reports it elsewhere? pub extern "C" fn transparent_str(p: TransparentStr) { } //~^ ERROR: uses type `str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } +// ^ possible FIXME: it doesn't see the actual FnPtr's error... +// but at least it reports it elsewhere? pub extern "C" fn good3(fptr: Option) { } -pub extern "C" fn good4(aptr: &[u8; 4 as usize]) { } +pub extern "C" fn argument_with_assumptions_4(aptr: &[u8; 4 as usize]) { } pub extern "C" fn good5(s: StructWithProjection) { } -pub extern "C" fn good6(s: StructWithProjectionAndLifetime) { } +pub extern "C" fn argument_with_assumptions_6(s: StructWithProjectionAndLifetime) { } pub extern "C" fn good7(fptr: extern "C" fn() -> ()) { } @@ -138,7 +141,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 c6ca382e435a1..4993edc7a77a0 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -7,10 +7,10 @@ LL | pub extern "C" fn slice_type(p: &[u32]) { } = help: consider using a raw pointer instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-fn.rs:2:9 + --> $DIR/lint-fn.rs:2:26 | -LL | #![deny(improper_ctypes_definitions, improper_ctypes)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `str`, which is not FFI-safe --> $DIR/lint-fn.rs:70:31 @@ -114,7 +114,7 @@ LL | pub extern "C" fn fn_type(p: RustFn) { } | ^^^^^^ not FFI-safe | = help: consider using an `extern fn(...) -> ...` function pointer instead - = note: this function pointer has Rust-specific calling convention + = note: this function pointer has a Rust-specific calling convention error: `extern` fn uses type `fn()`, which is not FFI-safe --> $DIR/lint-fn.rs:109:31 @@ -123,10 +123,10 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } | ^^^^ not FFI-safe | = help: consider using an `extern fn(...) -> ...` function pointer instead - = note: this function pointer has Rust-specific calling convention + = note: this function pointer has a Rust-specific calling convention error: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/lint-fn.rs:114:38 + --> $DIR/lint-fn.rs:115:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe @@ -135,7 +135,7 @@ LL | pub extern "C" fn transparent_str(p: TransparentStr) { } = note: string slices have no C equivalent error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:164:43 + --> $DIR/lint-fn.rs:167:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,7 +143,7 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:177:39 + --> $DIR/lint-fn.rs:180:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -152,7 +152,7 @@ LL | pub extern "C" fn used_generic4(x: Vec) { } = note: this struct has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:180:41 + --> $DIR/lint-fn.rs:183:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe From ad23745118a43da4e9d55779a601d6eef5fd18d9 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:41:01 +0200 Subject: [PATCH 04/17] ImproperCTypes: change cstr linting another user-visible change: change the messaging and help around CStr/CString lints --- .../rustc_lint/src/types/improper_ctypes.rs | 114 +++++++++++++----- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 2 +- tests/ui/lint/improper-ctypes/lint-cstr.rs | 32 ++--- .../ui/lint/improper-ctypes/lint-cstr.stderr | 58 ++++++--- 4 files changed, 141 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9707d843f024c..13ac7799a5262 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -366,7 +366,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) => { @@ -535,6 +534,12 @@ bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum OuterTyKind { None, + /// Pointee through ref, raw pointer or Box + /// (we don't need to distinguish the ownership of Box specifically) + Pointee { + mutable: hir::Mutability, + raw: bool, + }, /// For struct/enum/union fields AdtField, /// Placeholder for properties that will be used eventually @@ -546,16 +551,17 @@ impl OuterTyKind { fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { ty::FnPtr(..) => Self::None, + k @ (ty::Ref(_, _, mutable) | ty::RawPtr(_, mutable)) => { + Self::Pointee { raw: matches!(k, ty::RawPtr(..)), mutable: *mutable } + } ty::Adt(..) => { if ty.boxed_ty().is_some() { - Self::Other + Self::Pointee { raw: false, mutable: hir::Mutability::Mut } } else { Self::AdtField } } - ty::RawPtr(..) | ty::Ref(..) | ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => { - Self::Other - } + ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::Other, _ => bug!("Unexpected outer type {ty:?}"), } } @@ -666,6 +672,11 @@ impl VisitorState { fn is_field(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::AdtField) } + + /// Whether the current type is behind a pointer that doesn't allow mutating this + fn is_nonmut_pointee(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::Pointee { mutable: hir::Mutability::Not, .. }) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -676,22 +687,37 @@ struct ImproperCTypesVisitor<'a, 'tcx> { /// To prevent problems with recursive types, /// add a types-in-check cache. cache: FxHashSet>, - /// The original type being checked, before we recursed - /// to any other types it contains. - base_ty: Ty<'tcx>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_ty: Unnormalized<'tcx, Ty<'tcx>>) -> Self { - // Skip normalization for opaques: even in `TypingMode::Borrowck` the body's own - // defining opaques still get revealed, leaving entries in `OpaqueTypeStorage` that - // ICE on `InferCtxt` drop (issue #156352). - let base_ty = if base_ty.skip_norm_wip().has_opaque_types() { - base_ty.skip_norm_wip() + fn new(cx: &'a LateContext<'tcx>) -> Self { + ImproperCTypesVisitor { cx, cache: FxHashSet::default() } + } + + /// Return the right help for Cstring and Cstr-linked unsafety. + fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + debug_assert!(matches!(ty.kind(), ty::Adt(def, _) + if matches!( + self.cx.tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + )); + + let help = if state.is_nonmut_pointee() { + msg!( + "consider passing a `*const std::ffi::c_char` instead, converting to/from `{$ty}` as needed" + ) } else { - maybe_normalize_erasing_regions(cx, base_ty) + msg!( + "consider passing a `*mut std::ffi::c_char` instead, converting to/from `{$ty}` as needed" + ) }; - ImproperCTypesVisitor { cx, base_ty, cache: FxHashSet::default() } + + FfiResult::new_with_reason( + ty, + msg!("`CStr`/`CString` do not have a guaranteed layout"), + Some(help), + ) } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -705,6 +731,41 @@ 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.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 => msg!( + "this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + IndirectionKind::Ref => msg!( + "this reference to an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + IndirectionKind::Box => msg!( + "this box for an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + }; + 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: @@ -952,15 +1013,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, - msg!("`CStr`/`CString` do not have a guaranteed layout"), - Some(msg!( - "consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`" - )), - ); + return self.visit_cstr(state, ty); } self.visit_struct_or_union(state, ty, def, args) } @@ -1277,7 +1331,7 @@ impl<'tcx> ImproperCTypesLint { /// Check that an extern "ABI" static variable is of a ffi-safe type. fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(VisitorState::static_entry_point(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1294,20 +1348,18 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig.skip_norm_wip()); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let input_ty = Unnormalized::new_wip(*input_ty); let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx, input_ty); - let ffi_res = visitor.check_type(state, input_ty); + let mut visitor = ImproperCTypesVisitor::new(cx); + let ffi_res = visitor.check_type(state, Unnormalized::new_wip(*input_ty)); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let output_ty = Unnormalized::new_wip(sig.output()); let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx, output_ty); - let ffi_res = visitor.check_type(state, output_ty); + let mut visitor = ImproperCTypesVisitor::new(cx); + let ffi_res = visitor.check_type(state, Unnormalized::new_wip(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 753c8a90ddf24..d5a7cfcb119b9 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -4,7 +4,7 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default diff --git a/tests/ui/lint/improper-ctypes/lint-cstr.rs b/tests/ui/lint/improper-ctypes/lint-cstr.rs index b04decd0bcacc..4fb660cb9e13c 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.rs +++ b/tests/ui/lint/improper-ctypes/lint-cstr.rs @@ -6,31 +6,35 @@ use std::ffi::{CStr, CString}; extern "C" { fn take_cstr(s: CStr); //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe - //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed fn take_cstr_ref(s: &CStr); - //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe - //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + //~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed fn take_cstring(s: CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe - //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed fn take_cstring_ref(s: &CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe - //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + //~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed - fn no_special_help_for_mut_cstring(s: *mut CString); + fn take_cstring_ptr_mut(s: *mut CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe - //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed - fn no_special_help_for_mut_cstring_ref(s: &mut CString); + fn take_cstring_ref_mut(s: &mut CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe - //~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed } extern "C" fn rust_take_cstr_ref(s: &CStr) {} -//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe -//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +//~^ ERROR `extern` fn uses type `&CStr`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed extern "C" fn rust_take_cstring(s: CString) {} //~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe -//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` -extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {} -extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {} +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed +extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed +extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed diff --git a/tests/ui/lint/improper-ctypes/lint-cstr.stderr b/tests/ui/lint/improper-ctypes/lint-cstr.stderr index da26306584311..1907d41c858a8 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.stderr +++ b/tests/ui/lint/improper-ctypes/lint-cstr.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `CStr`, which is not FFI-safe LL | fn take_cstr(s: CStr); | ^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-cstr.rs:2:9 @@ -12,13 +12,14 @@ note: the lint level is defined here LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `CStr`, which is not FFI-safe +error: `extern` block uses type `&CStr`, which is not FFI-safe --> $DIR/lint-cstr.rs:10:25 | LL | fn take_cstr_ref(s: &CStr); | ^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe @@ -27,7 +28,7 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn take_cstring(s: CString); | ^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe @@ -36,34 +37,35 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn take_cstring_ref(s: &CString); | ^^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-cstr.rs:20:43 + --> $DIR/lint-cstr.rs:20:32 | -LL | fn no_special_help_for_mut_cstring(s: *mut CString); - | ^^^^^^^^^^^^ not FFI-safe +LL | fn take_cstring_ptr_mut(s: *mut CString); + | ^^^^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-cstr.rs:24:47 + --> $DIR/lint-cstr.rs:24:32 | -LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString); - | ^^^^^^^^^^^^ not FFI-safe +LL | fn take_cstring_ref_mut(s: &mut CString); + | ^^^^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout -error: `extern` fn uses type `CStr`, which is not FFI-safe +error: `extern` fn uses type `&CStr`, which is not FFI-safe --> $DIR/lint-cstr.rs:29:37 | LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {} | ^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-cstr.rs:2:26 @@ -77,8 +79,26 @@ error: `extern` fn uses type `CString`, which is not FFI-safe LL | extern "C" fn rust_take_cstring(s: CString) {} | ^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-cstr.rs:35:44 + | +LL | extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-cstr.rs:38:44 + | +LL | extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout -error: aborting due to 8 previous errors +error: aborting due to 10 previous errors From c2c87c0d7fb671891f7ee5e19bf987930b7f8c63 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:57:30 +0200 Subject: [PATCH 05/17] 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. --- .../rustc_lint/src/types/improper_ctypes.rs | 290 ++++++++++++++---- tests/ui/lint/extern-C-fnptr-lints-slices.rs | 2 +- .../lint/extern-C-fnptr-lints-slices.stderr | 4 +- tests/ui/lint/improper-ctypes/lint-73249-2.rs | 5 +- .../lint/improper-ctypes/lint-73249-2.stderr | 15 - tests/ui/lint/improper-ctypes/lint-ctypes.rs | 46 ++- .../lint/improper-ctypes/lint-ctypes.stderr | 114 +++---- tests/ui/lint/improper-ctypes/lint-fn.rs | 12 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 64 ++-- 9 files changed, 349 insertions(+), 203 deletions(-) delete mode 100644 tests/ui/lint/improper-ctypes/lint-73249-2.stderr diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 13ac7799a5262..97cdc912ad26c 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -499,7 +499,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). @@ -510,6 +510,156 @@ enum IndirectionKind { RawPtr, } +/// The different ways a given type can have/not have a fixed size. +/// Relies on the vocabulary of the Hierarchy of Sized Traits change (`#![feature(sized_hierarchy)]`) +#[derive(Clone, Copy)] +enum TypeSizedness { + /// Type of definite size (pointers are C-compatible). + Sized, + /// Unsized type because it includes an opaque/foreign type (pointers are C-compatible). + /// (Relies on all Unsized types being `extern` types, and unable to be used in an array/slice) + Unsized, + /// MetaSized types are types whose size can be computed from pointer metadata (slice, string, dyn Trait, closure, ...) + /// (pointers are not C-compatible). + MetaSized, + /// Not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like). + NotYetKnown, +} + +/// Determine if a type is sized or not, and whether it affects references/pointers/boxes to it. +fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { + let tcx = cx.tcx; + + // note that sizedness is unrelated to inhabitedness + if ty.is_sized(tcx, cx.typing_env()) { + TypeSizedness::Sized + } else { + // the overall type is !Sized or ?Sized + match ty.kind() { + ty::Slice(_) | ty::Str | ty::Dynamic(..) => TypeSizedness::MetaSized, + ty::Foreign(..) => TypeSizedness::Unsized, + ty::Adt(def, args) => { + // for now assume: boxes and phantoms don't mess with this + match def.adt_kind() { + AdtKind::Union | AdtKind::Enum => { + bug!("unions and enums are necessarily sized") + } + AdtKind::Struct => { + if let Some(intermediate) = + def.sizedness_constraint(tcx, ty::SizedTraitKind::MetaSized) + { + let ty = maybe_normalize_erasing_regions( + cx, + intermediate.instantiate(tcx, args), + ); + get_type_sizedness(cx, ty) + } else { + debug_assert!( + def.sizedness_constraint(tcx, ty::SizedTraitKind::Sized).is_some() + ); + TypeSizedness::MetaSized + } + + // if let Some(sym::cstring_type | sym::cstr_type) = + // tcx.get_diagnostic_name(def.did()) + // { + // return TypeSizedness::MetaSized; + // } + + // // note: non-exhaustive structs from other crates are not assumed to be ?Sized + // // for the purpose of sizedness, it seems we are allowed to look at its current contents. + + // if def.non_enum_variant().fields.is_empty() { + // bug!("an empty struct is necessarily sized"); + // } + + // let variant = def.non_enum_variant(); + + // // only the last field may be !Sized (or ?Sized in the case of type params) + // let last_field = match (&variant.fields).iter().last() { + // Some(last_field) => last_field, + // // even nonexhaustive-empty structs from another crate are considered Sized + // // (eventhough one could add a !Sized field to them) + // None => bug!("Empty struct should be Sized, right?"), // + // }; + // let field_ty = get_type_from_field(cx, last_field, args); + // match get_type_sizedness(cx, field_ty) { + // s @ (TypeSizedness::MetaSized + // | TypeSizedness::Unsized + // | TypeSizedness::NotYetKnown) => s, + // TypeSizedness::Sized => { + // bug!("failed to find the reason why struct `{:?}` is unsized", ty) + // } + // } + } + } + } + ty::Tuple(tuple) => { + // only the last field may be !Sized (or ?Sized in the case of type params) + let item_ty: Unnormalized<'tcx, Ty<'tcx>> = match tuple.last() { + Some(item_ty) => Unnormalized::new_wip(*item_ty), + None => bug!("Empty tuple (AKA unit type) should be Sized, right?"), + }; + let item_ty = maybe_normalize_erasing_regions(cx, item_ty); + match get_type_sizedness(cx, item_ty) { + s @ (TypeSizedness::MetaSized + | TypeSizedness::Unsized + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Sized => { + bug!("failed to find the reason why tuple `{:?}` is unsized", ty) + } + } + } + + ty::Pat(base, _) => get_type_sizedness(cx, *base), + + ty_kind @ (ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Array(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) + | ty::Never) => { + // those types are all sized, right? + bug!( + "This ty_kind (`{:?}`) should be sized, yet we are in a branch of code that deals with unsized types.", + ty_kind, + ) + } + + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach ty::Alias(ty::Opaque) here. + ty::Param(..) + | ty::Alias( + _, + ty::AliasTy { + kind: ty::Opaque { .. } | ty::Projection { .. } | ty::Inherent { .. }, + .. + }, + ) => { + return TypeSizedness::NotYetKnown; + } + + // we can skip the binder, it only binds lifetimes, which we don't care about here + ty::UnsafeBinder(inner) => get_type_sizedness(cx, inner.skip_binder()), + + ty::Alias(_, ty::AliasTy { kind: ty::Free { .. }, .. }) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Placeholder(..) + | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + } + } +} + bitflags! { /// VisitorState flags that are linked with the root type's use. /// (These are the permanent part of the state, kept when visiting new Ty.) @@ -655,13 +805,6 @@ impl VisitorState { self.root_use_flags.contains(RootUseFlags::DEFINED) && self.is_in_function() } - /// Whether the type is used (directly or not) in a function pointer type. - /// Here, we also allow non-FFI-safe types behind a C pointer, - /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_fnptr(&self) -> bool { - self.root_use_flags.contains(RootUseFlags::THEORETICAL) && self.is_in_function() - } - /// Whether we can expect type parameters and co in a given type. fn can_expect_ty_params(&self) -> bool { // rust-defined functions, as well as FnPtrs @@ -677,6 +820,11 @@ impl VisitorState { fn is_nonmut_pointee(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::Pointee { mutable: hir::Mutability::Not, .. }) } + + /// Whether the current type is behind a raw pointer + fn is_raw_pointee(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::Pointee { raw: true, .. }) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -686,12 +834,12 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// To prevent problems with recursive types, /// add a types-in-check cache. - cache: FxHashSet>, + ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> Self { - ImproperCTypesVisitor { cx, cache: FxHashSet::default() } + ImproperCTypesVisitor { cx, ty_cache: FxHashSet::default() } } /// Return the right help for Cstring and Cstr-linked unsafety. @@ -728,7 +876,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() { @@ -766,60 +913,65 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - match indirection_kind { - IndirectionKind::Box => { - // FIXME(ctypes): this logic is broken, but it still fits the current tests: - // - for some reason `Box<_>`es in `extern "ABI" {}` blocks - // are not treated as pointers but as FFI-unsafe structs - // - otherwise, treat the box itself correctly, and follow pointee safety logic - // as described in the other `indirection_type` match branch. - if state.is_in_defined_function() || state.is_in_fnptr() { - if inner_ty.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; - } else { - return FfiResult::new_with_reason( - ty, - msg!("box cannot be represented as a single pointer"), - None, - ); - } - } else { - // (mid-retcon-commit-chain comment:) - // this is the original fallback behavior, which is wrong - if let ty::Adt(def, args) = ty.kind() { - self.visit_struct_or_union(state, ty, *def, args) - } else if cfg!(debug_assertions) { - bug!("ImproperCTypes: this retcon commit was badly written") - } else { - FfiSafe - } + // there are three remaining concerns with the pointer: + // - is the pointer compatible with a C pointer in the first place? (if not, only send that error message) + // - is the pointee FFI-safe? (it might not matter, see mere lines below) + // - does the pointer type contain a non-zero assumption, but has a value given by non-rust code? + // this block deals with the first two. + let type_sizedness = get_type_sizedness(self.cx, inner_ty); + match type_sizedness { + TypeSizedness::Unsized | TypeSizedness::Sized => { + if matches!( + (type_sizedness, indirection_kind), + (TypeSizedness::Unsized, IndirectionKind::Box) + ) { + // Box<_> means rust is capable of drop()'ing the pointee, + // which is impossible for `extern` types (foreign opaque types). + bug!( + "FFI-unsafeties similar to `Box` currently cause compilation errors that should prevent ImproperCTypes from running. If you see this, it is likely this behaviour has changed." + ); } + // FIXME(ctypes): + // for now, we consider this to be safe even in the case of a FFI-unsafe pointee + // this is technically only safe if the pointer is never dereferenced on the non-rust + // side of the FFI boundary, i.e. if the type is to be treated as opaque + // there are techniques to flag those pointees as opaque, but not always, so we can only enforce this + // in some cases. + FfiResult::FfiSafe } - IndirectionKind::Ref | IndirectionKind::RawPtr => { - // Weird behaviour for pointee safety. the big question here is - // "if you have a FFI-unsafe pointee behind a FFI-safe pointer type, is it ok?" - // The answer until now is: - // "It's OK for rust-defined functions and callbacks, we'll assume those are - // meant to be opaque types on the other side of the FFI boundary". - // - // Reasoning: - // For extern function declarations, the actual definition of the function is - // written somewhere else, meaning the declaration is free to express this - // opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void - // (opaque callee-side). For extern function definitions, however, in the case - // where the type is opaque caller-side, it is not opaque callee-side, - // and having the full type information is necessary to compile the function. - // - // It might be better to rething this, or even ignore pointee safety for a first - // batch of behaviour changes. See the discussion that ends with - // https://github.com/rust-lang/rust/pull/134697#issuecomment-2692610258 - if (state.is_in_defined_function() || state.is_in_fnptr()) - && inner_ty.is_sized(self.cx.tcx, self.cx.typing_env()) - { - FfiSafe - } else { - self.visit_type(state.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.next(ty), inner_ty) + } + TypeSizedness::MetaSized => { + let help = match inner_ty.kind() { + ty::Str => Some(msg!("consider using `*const u8` and a length instead")), + ty::Slice(_) => Some(msg!("consider using a raw pointer instead")), + _ => None, + }; + let reason = match indirection_kind { + IndirectionKind::RawPtr => msg!( + "this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + IndirectionKind::Ref => msg!( + "this reference to an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + IndirectionKind::Box => msg!( + "this box for an unsized type contains metadata, which makes it incompatible with a C pointer" + ), + }; + return FfiResult::new_with_reason(ty, reason, help); } } } @@ -996,7 +1148,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; } @@ -1011,6 +1164,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()) { @@ -1064,7 +1222,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { && matches!(state.outer_ty_kind, OuterTyKind::None) ) // `()` fields are safe - || state.is_field()) + || state.is_field() + // this serves as a "void*" + || state.is_raw_pointee()) { FfiSafe } else { diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 08db0539ab4e7..bf0a754f81022 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.rs +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.rs @@ -3,7 +3,7 @@ // It's an improper ctype (a slice) arg in an extern "C" fnptr. pub type F = extern "C" fn(&[u8]); -//~^ ERROR: `extern` callback uses type `[u8]`, which is not FFI-safe +//~^ ERROR: `extern` callback uses type `&[u8]`, which is not FFI-safe fn main() {} diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index 6a36ba6c28d6f..f0c0cc8167fd0 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,11 +1,11 @@ -error: `extern` callback uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `&[u8]`, which is not FFI-safe --> $DIR/extern-C-fnptr-lints-slices.rs:5:28 | LL | pub type F = extern "C" fn(&[u8]); | ^^^^^ not FFI-safe | = help: consider using a raw pointer instead - = note: slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 | diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.rs b/tests/ui/lint/improper-ctypes/lint-73249-2.rs index 31af0e3d381ef..9286d822e22e3 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-2.rs @@ -1,3 +1,5 @@ +//@ check-pass // possible FIXME: see below + #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] @@ -24,7 +26,8 @@ struct A { } extern "C" { - fn lint_me() -> A<()>; //~ ERROR: uses type `Qux` + // possible FIXME(ctypes): the unsafety of Qux is unseen, as it is behing a FFI-safe indirection + fn lint_me() -> A<()>; } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.stderr b/tests/ui/lint/improper-ctypes/lint-73249-2.stderr deleted file mode 100644 index d6c1cec2bd6c1..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73249-2.rs:27:21 - | -LL | fn lint_me() -> A<()>; - | ^^^^^ not FFI-safe - | - = note: opaque types have no C equivalent -note: the lint level is defined here - --> $DIR/lint-73249-2.rs:2:9 - | -LL | #![deny(improper_ctypes)] - | ^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error - diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 473ac4bb0d7d4..6404ca46072fe 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +#![feature(extern_types)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -6,7 +7,9 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; +use std::fmt::Debug; +unsafe extern "C" {type UnsizedOpaque;} trait Bar { } trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } @@ -20,7 +23,7 @@ pub type I32Pair = (i32, i32); #[repr(C)] pub struct ZeroSize; pub type RustFn = fn(); -pub type RustBadRet = extern "C" fn() -> Box; +pub type RustBoxRet = extern "C" fn() -> Box; pub type CVoidRet = (); pub struct Foo; #[repr(transparent)] @@ -28,7 +31,7 @@ pub struct TransparentI128(i128); #[repr(transparent)] pub struct TransparentStr(&'static str); #[repr(transparent)] -pub struct TransparentBadFn(RustBadRet); +pub struct TransparentBoxFn(RustBoxRet); #[repr(transparent)] pub struct TransparentInt(u32); #[repr(transparent)] @@ -37,21 +40,37 @@ pub struct TransparentRef<'a>(&'a TransparentInt); pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>); #[repr(transparent)] pub struct TransparentUnit(f32, PhantomData); +#[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` @@ -61,11 +80,14 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); + pub fn fn_contained(p: RustBoxRet); + pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` + pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` + pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); + pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` + pub fn no_niche_a(a: Option>); //~^ ERROR: uses type `Option>` pub fn no_niche_b(b: Option>); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index e35b1cb5d0101..ca865668d41a4 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:45:28 +error: `extern` block uses type `&[u32]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:68:26 | -LL | pub fn ptr_type1(size: *const Foo); - | ^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout -note: the type is defined here - --> $DIR/lint-ctypes.rs:25:1 +LL | pub fn slice_type(p: &[u32]); + | ^^^^^^ not FFI-safe | -LL | pub struct Foo; - | ^^^^^^^^^^^^^^ + = help: consider using a raw pointer instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here - --> $DIR/lint-ctypes.rs:4:9 + --> $DIR/lint-ctypes.rs:5:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:46: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:48: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:49:26 - | -LL | pub fn slice_type(p: &[u32]); - | ^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:50:24 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:69: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:51: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:53:25 + --> $DIR/lint-ctypes.rs:72: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:54:26 +error: `extern` block uses type `&dyn Bar`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:73: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:55:26 + --> $DIR/lint-ctypes.rs:74: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:56:27 + --> $DIR/lint-ctypes.rs:75: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:57:25 + --> $DIR/lint-ctypes.rs:76:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -111,26 +65,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: this struct has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:21:1 + --> $DIR/lint-ctypes.rs:24:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:58:33 + --> $DIR/lint-ctypes.rs:77: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:42:1 + --> $DIR/lint-ctypes.rs:61:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:61:12 + --> $DIR/lint-ctypes.rs:80: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:62:23 + --> $DIR/lint-ctypes.rs:81: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 a Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:63:24 + --> $DIR/lint-ctypes.rs:82:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -155,17 +109,17 @@ LL | pub fn fn_type2(p: fn()); = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has a Rust-specific calling convention -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:65:31 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:84:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:67:27 + --> $DIR/lint-ctypes.rs:86:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -173,8 +127,16 @@ LL | pub fn raw_array(arr: [u8; 8]); = help: consider passing a pointer to the array = note: passing raw arrays by value is not FFI-safe +error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:89: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:69:26 + --> $DIR/lint-ctypes.rs:91:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -183,7 +145,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:26 + --> $DIR/lint-ctypes.rs:93:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -191,5 +153,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 19 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index d2cde2f215c56..c2624ebcefe7c 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 = (); @@ -65,14 +65,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]>` @@ -110,14 +111,11 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } -// ^ FIXME it doesn't see the error... but at least it reports it elsewhere? pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } -// ^ possible FIXME: it doesn't see the actual FnPtr's error... -// but at least it reports it elsewhere? pub extern "C" fn good3(fptr: Option) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 4993edc7a77a0..0659bd0d0e1c0 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -1,52 +1,68 @@ -error: `extern` fn uses type `[u32]`, which is not FFI-safe +error: `extern` callback uses type `(u32, u64)`, which is not FFI-safe + --> $DIR/lint-fn.rs:29:42 + | +LL | pub type RustBadRet = extern "C" fn() -> (u32,u64); + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/lint-fn.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&[u32]`, which is not FFI-safe --> $DIR/lint-fn.rs:67:33 | LL | pub extern "C" fn slice_type(p: &[u32]) { } | ^^^^^^ not FFI-safe | = help: consider using a raw pointer instead - = note: slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-fn.rs:2:26 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:70: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:77:34 + --> $DIR/lint-fn.rs:78: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:80:35 + --> $DIR/lint-fn.rs:81: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:83:34 + --> $DIR/lint-fn.rs:84: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:86:32 + --> $DIR/lint-fn.rs:87: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:89:33 + --> $DIR/lint-fn.rs:90: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:92:34 + --> $DIR/lint-fn.rs:93: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:95:32 + --> $DIR/lint-fn.rs:96: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:98:40 + --> $DIR/lint-fn.rs:99: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:101:51 + --> $DIR/lint-fn.rs:102: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:106:30 + --> $DIR/lint-fn.rs:107: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 a Rust-specific calling convention error: `extern` fn uses type `fn()`, which is not FFI-safe - --> $DIR/lint-fn.rs:109:31 + --> $DIR/lint-fn.rs:110:31 | LL | pub extern "C" fn fn_type2(p: fn()) { } | ^^^^ not FFI-safe @@ -125,17 +141,17 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has a Rust-specific calling convention -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:115:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:167:43 + --> $DIR/lint-fn.rs:165:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,7 +159,7 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:180:39 + --> $DIR/lint-fn.rs:178:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -152,7 +168,7 @@ LL | pub extern "C" fn used_generic4(x: Vec) { } = note: this struct has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:183:41 + --> $DIR/lint-fn.rs:181:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe @@ -160,5 +176,5 @@ LL | pub extern "C" fn used_generic5() -> Vec { = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout -error: aborting due to 17 previous errors +error: aborting due to 18 previous errors From ee88e3644e1d847d380d4533223624e38ceae6bb Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 27 Aug 2025 22:30:22 +0200 Subject: [PATCH 06/17] 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 --- .../rustc_lint/src/types/improper_ctypes.rs | 26 ++++++++---- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 1 + tests/ui/extern/extern-C-str-arg-ice-80125.rs | 3 +- .../ui/lint/improper-ctypes/lint-113436-1.rs | 4 +- .../lint/improper-ctypes/lint-113436-1.stderr | 16 ++++++- tests/ui/lint/improper-ctypes/lint-73249-3.rs | 2 +- .../lint/improper-ctypes/lint-73249-3.stderr | 8 +++- tests/ui/lint/improper-ctypes/lint-73249-5.rs | 2 +- .../lint/improper-ctypes/lint-73249-5.stderr | 2 +- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 8 +++- .../lint/improper-ctypes/lint-ctypes.stderr | 42 ++++++++++++++++--- tests/ui/lint/improper-ctypes/lint-fn.rs | 2 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 2 +- .../lint-non-recursion-limit.rs | 2 +- .../lint-non-recursion-limit.stderr | 14 ++++++- .../improper-ctypes/repr-rust-is-undefined.rs | 6 +-- .../repr-rust-is-undefined.stderr | 24 +++++++++-- 17 files changed, 132 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 97cdc912ad26c..bf72d645950a6 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -446,7 +446,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::*; @@ -1009,7 +1008,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { all_phantom &= match self.visit_type(state.next(ty), field_ty) { FfiSafe => false, FfiPhantom(..) => true, - r @ FfiUnsafe { .. } => return r, + r @ FfiUnsafe { .. } => { + return r.wrap_all(ty, msg!("this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field"), None); + } } } @@ -1068,7 +1069,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() { @@ -1084,7 +1085,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ) } else { self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) - } + }; + + // Here, if there is something wrong, then the "fault" comes from inside the struct itself. + // Even if we add more details to the lint, the initial line must specify that + // the FFI-unsafety is because of the struct + // Plus, if the struct is from another crate, then there's not much that can be done anyways + // + // So, we override the "cause type" of the lint. + ffires.with_overrides(Some(ty)) } fn visit_enum( @@ -1133,10 +1142,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 e9aa6898ec766..acd7e4f2d1691 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 @@ -10,6 +10,7 @@ type Foo = extern "C" fn(::std::ffi::CStr); //~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); + // ^ FIXME: the error isn't seen here but at least it's reported elsewhere } fn main() { diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 1c1abbe996839..fa300ba9d173b 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -7,7 +7,8 @@ pub struct Struct(ExternCallback); #[no_mangle] pub extern "C" fn register_something(bind: ExternCallback) -> Struct { -//~^ WARN `extern` fn uses type `Struct`, which is not FFI-safe +// ^ FIXME: the error isn't seen here, but at least it's reported elsewhere +//~^^ WARN `extern` fn uses type `Struct`, which is not FFI-safe Struct(bind) } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/lint-113436-1.rs index 1ca59c6868d6d..27dcd0184d90f 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.rs +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.rs @@ -20,8 +20,8 @@ pub struct Bar { } extern "C" fn bar(x: Bar) -> Bar { - //~^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe - //~^^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe + //~^ ERROR `extern` fn uses type `Bar`, which is not FFI-safe + //~^^ ERROR `extern` fn uses type `Bar`, which is not FFI-safe todo!() } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr index f01dc3b6e0d1e..b230475df1c6b 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr @@ -1,9 +1,15 @@ -error: `extern` fn uses type `NotSafe`, which is not FFI-safe +error: `extern` fn uses type `Bar`, which is not FFI-safe --> $DIR/lint-113436-1.rs:22:22 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -17,12 +23,18 @@ note: the lint level is defined here LL | #![deny(improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `NotSafe`, which is not FFI-safe +error: `extern` fn uses type `Bar`, which is not FFI-safe --> $DIR/lint-113436-1.rs:22:30 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.rs b/tests/ui/lint/improper-ctypes/lint-73249-3.rs index 8bdf536bf77e6..aff2a182e3f49 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-3.rs @@ -18,7 +18,7 @@ pub struct A { } extern "C" { - pub fn lint_me() -> A; //~ ERROR: uses type `Qux` + pub fn lint_me() -> A; //~ ERROR: `extern` block uses type `A` } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr b/tests/ui/lint/improper-ctypes/lint-73249-3.stderr index ebc9eb5eb8274..dc6f6fb08ed33 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-3.stderr @@ -1,9 +1,15 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe +error: `extern` block uses type `A`, which is not FFI-safe --> $DIR/lint-73249-3.rs:21:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | + = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field +note: the type is defined here + --> $DIR/lint-73249-3.rs:16:1 + | +LL | pub struct A { + | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-73249-3.rs:2:9 diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.rs b/tests/ui/lint/improper-ctypes/lint-73249-5.rs index cc6da59950d7a..8ad5be4e6301e 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.rs @@ -18,7 +18,7 @@ pub struct A { } extern "C" { - pub fn lint_me() -> A; //~ ERROR: uses type `Qux` + pub fn lint_me() -> A; //~ ERROR: uses type `A` } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr index 484927f57fead..df2da2da1e37f 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr @@ -1,4 +1,4 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe +error: `extern` block uses type `A`, which is not FFI-safe --> $DIR/lint-73249-5.rs:21:25 | LL | pub fn lint_me() -> A; diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 6404ca46072fe..42d213a05b53d 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -81,9 +81,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 ca865668d41a4..b2bf61461e8f2 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 a 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:84:31 | LL | pub fn transparent_str(p: TransparentStr); @@ -127,8 +127,40 @@ 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:88:36 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:88:44 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^ not FFI-safe + | + = 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:88:59 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `char` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:55:1 + | +LL | pub struct TwoBadTypes<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:89:47 + --> $DIR/lint-ctypes.rs:95:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -136,7 +168,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:91:26 + --> $DIR/lint-ctypes.rs:97:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -145,7 +177,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:93:26 + --> $DIR/lint-ctypes.rs:99:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -153,5 +185,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 c2624ebcefe7c..07e2b41ba3650 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -113,7 +113,7 @@ pub extern "C" fn fn_type2(p: fn()) { } pub extern "C" fn fn_contained(p: RustBadRet) { } pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `&str` +//~^ ERROR: uses type `TransparentStr` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 0659bd0d0e1c0..afd4bccf5aece 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -141,7 +141,7 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has a 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:115:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs index 9aa9be052d761..acbbb8363a470 100644 --- a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs @@ -33,6 +33,6 @@ struct B { } extern "C" fn foo(_: B) {} -//~^ ERROR: uses type `char` +//~^ ERROR: uses type `B` fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr index 7a47da8a788f6..392ac2e6f4123 100644 --- a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr @@ -1,9 +1,21 @@ -error: `extern` fn uses type `char`, which is not FFI-safe +error: `extern` fn uses type `B`, which is not FFI-safe --> $DIR/lint-non-recursion-limit.rs:35:22 | LL | extern "C" fn foo(_: B) {} | ^ not FFI-safe | + = note: this struct/enum/union (`B`) is FFI-unsafe due to a `F6` field +note: the type is defined here + --> $DIR/lint-non-recursion-limit.rs:23:1 + | +LL | struct B { + | ^^^^^^^^ + = note: this struct/enum/union (`F6`) is FFI-unsafe due to a `char` field +note: the type is defined here + --> $DIR/lint-non-recursion-limit.rs:20:1 + | +LL | struct F6([char;8]); //oops! + | ^^^^^^^^^ = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent note: the lint level is defined here 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 fcb5ca098cfaac760206e30a16ef3be596a27f99 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:13:02 +0200 Subject: [PATCH 07/17] 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/src/types.rs | 20 ++ .../rustc_lint/src/types/improper_ctypes.rs | 252 +++++++++++------- .../extern/extern-C-str-arg-ice-80125.stderr | 4 +- tests/ui/extern/issue-16250.stderr | 4 +- .../lint/improper-ctypes/lint-113436-1.stderr | 8 +- .../lint/improper-ctypes/lint-73249-5.stderr | 6 + .../ui/lint/improper-ctypes/lint-94223.stderr | 16 +- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 1 + .../lint/improper-ctypes/lint-ctypes.stderr | 31 ++- 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 +- tests/ui/lint/lint-gpu-kernel.amdgpu.stderr | 4 +- tests/ui/lint/lint-gpu-kernel.nvptx.stderr | 4 +- .../repr/repr-transparent-issue-87496.stderr | 2 +- .../extern_crate_improper.stderr | 6 +- tests/ui/union/union-repr-c.stderr | 4 +- 18 files changed, 309 insertions(+), 138 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/src/types.rs b/compiler/rustc_lint/src/types.rs index 27642a5c3b212..14b8bbbee21fd 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -722,6 +722,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().skip_norm_wip(); + 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 bf72d645950a6..4f2dcb1b1f95f 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -187,37 +187,6 @@ fn maybe_normalize_erasing_regions<'tcx>( cx.tcx.try_normalize_erasing_regions(typing_env, value).unwrap_or(value.skip_norm_wip()) } -/// Check a variant of a non-exhaustive enum for improper ctypes -/// -/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". -/// This includes linting, on a best-effort basis. There are valid additions that are unlikely. -/// -/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", -/// so we don't need the lint to account for it. -/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. -pub(crate) fn check_non_exhaustive_variant( - non_exhaustive_variant_list: bool, - variant: &ty::VariantDef, -) -> ControlFlow { - // non_exhaustive suggests it is possible that someone might break ABI - // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 - // so warn on complex enums being used outside their crate - if non_exhaustive_variant_list { - // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 - // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` - // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) - if variant_has_complex_ctor(variant) { - return ControlFlow::Break(msg!("this enum is non-exhaustive")); - } - } - - if variant.field_list_has_applicable_non_exhaustive() { - return ControlFlow::Break(msg!("this enum has non-exhaustive variants")); - } - - ControlFlow::Continue(()) -} - fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { // CtorKind::Const means a "unit" ctor !matches!(variant.ctor_kind(), Some(CtorKind::Const)) @@ -388,7 +357,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) => { @@ -581,7 +549,10 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type // // (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); + // let field_ty = maybe_normalize_erasing_regions( + // cx, + // Unnormalized::new_wip(last_field.ty(cx.tcx, args)), + // ); // match get_type_sizedness(cx, field_ty) { // s @ (TypeSizedness::MetaSized // | TypeSizedness::Unsized @@ -986,42 +957,126 @@ 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 = - maybe_normalize_erasing_regions(self.cx, field.ty(self.cx.tcx, args)); - return self.visit_type(state.next(ty), field_ty); + // The decision tree for the safety of a list of fields is as follows: + // - is it neither `repr(C)`, `transparent` (for a struct), nor `repr(int_type)` (for enums)? + // - if so, it is unsafe. + // - are all the fields PhantomData? + // - if so, the struct as a whole is PhantomData + // - is it a transparent struct? + // - if so, are all fields 1ZSTs? + // - if so, it is unsafe in all cases (prefer reporting unsafeties from the fields, if any) + // - otherwise, check the remaining field's safety + // - otherwise, check the safety of all fields + // - if this is a `repr(C)` struct with only one non-1ZST field, + // which is safe, suggest using `repr(transparent)` instead + + 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 = maybe_normalize_erasing_regions(self.cx, field.ty(self.cx.tcx, args)); - all_phantom &= match self.visit_type(state.next(ty), field_ty) { - FfiSafe => false, + let ffi_res = self.visit_type(state.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, msg!("this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field"), None); + fields_ok_list[field_i] = false; + ffires_accumulator += r; + false } } } - if all_phantom { + // if we have bad fields, also report a possible transparent_with_all_zst_fields + // (if this combination is somehow possible) + // otherwise, having all fields be phantoms + // takes priority over transparent_with_all_zst_fields + if let FfiUnsafe(explanations) = ffires_accumulator { + debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some()); + + if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) { + let field_ffires = FfiUnsafe(explanations).wrap_all( + ty, + msg!("this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field"), + None, + ); + if transparent_with_all_zst_fields { + field_ffires + + FfiResult::new_with_reason( + ty, + msg!("`{$ty}` contains only zero-sized fields"), + 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 | AdtKind::Union => Some(msg!( + "`{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead" + )), + AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"), + } + } else { + None + }; + + FfiUnsafe(explanations).wrap_all( + ty, + msg!("this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field"), + help, + ) + } + } else if all_phantom { FfiPhantom(ty) } else if transparent_with_all_zst_fields { - FfiResult::new_with_reason( - ty, - msg!("this struct contains only zero-sized fields"), - None, - ) + FfiResult::new_with_reason(ty, msg!("`{$ty}` contains only zero-sized fields"), None) } else { FfiSafe } @@ -1039,44 +1094,27 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() { return FfiResult::new_with_reason( ty, - if def.is_struct() { - msg!("this struct has unspecified layout") - } else { - msg!("this union has unspecified layout") - }, + msg!("`{$ty}` has unspecified layout"), if def.is_struct() { Some(msg!( - "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct" + // TODO: discuss readability implications of repeating ty name on every message + "consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct" )) } else { // FIXME(#60405): confirm that this makes sense for unions once #60405 / RFC2645 stabilises - Some(msg!( - "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union" - )) + Some(msg!("consider adding a `#[repr(C)]` attribute to this union")) }, ); } if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { - return FfiResult::new_with_reason( - ty, - if def.is_struct() { - msg!("this struct is non-exhaustive") - } else { - msg!("this union is non-exhaustive") - }, - None, - ); + return FfiResult::new_with_reason(ty, msg!("`{$ty}` is non-exhaustive"), None); } let ffires = if def.non_enum_variant().fields.is_empty() { FfiResult::new_with_reason( ty, - if def.is_struct() { - msg!("this struct has no fields") - } else { - msg!("this union has no fields") - }, + msg!("`{$ty}` has no fields"), if def.is_struct() { Some(msg!("consider adding a member to this struct")) } else { @@ -1130,24 +1168,54 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // FIXME(ctypes): connect `def.repr().int` to visit_numeric // (for now it's OK, `repr(char)` doesn't exist and visit_numeric doesn't warn on anything else) - let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); + let enum_non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?; - match self.visit_variant_fields(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } + // non_exhaustive suggests it is possible that someone might break ABI + // See: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 + // so warn on complex enums being used outside their crate. + // + // We treat `#[non_exhaustive]` enum variants as unsafe if the enum is passed by-value, + // as additions it will change it size. + // + // We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". + // This includes linting, on a best-effort basis. There are valid additions that are unlikely. + // + // Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", + // so we don't need the lint to account for it. + // e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. + // Which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 + // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` + // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) + + let (mut improper_on_nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false); + def.variants().iter().for_each(|variant| { + improper_on_nonexhaustive_flag |= + enum_non_exhaustive && variant_has_complex_ctor(variant); + nonexhaustive_variant_flag |= variant.field_list_has_applicable_non_exhaustive(); }); - if let ControlFlow::Break(result) = ret { + + if improper_on_nonexhaustive_flag { + FfiResult::new_with_reason(ty, msg!("this enum is non-exhaustive"), None) + } else if nonexhaustive_variant_flag { + FfiResult::new_with_reason(ty, msg!("this enum has non-exhaustive variants"), None) + } else { + let ffires = def + .variants() + .iter() + .map(|variant| { + let variant_res = self.visit_variant_fields(state, ty, def, variant, args); + // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms? + // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?) + variant_res.forbid_phantom() + }) + .reduce(|r1, r2| r1 + r2) + .unwrap(); // always at least one variant if we hit this branch + // this enum is visited in the middle of another lint, // so we override the "cause type" of the lint - // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``) - result.with_overrides(Some(ty)) - } else { - FfiSafe + // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``) + ffires.with_overrides(Some(ty)) } } diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index 6eded6a78cb74..3f74389a5aec7 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -14,8 +14,8 @@ warning: `extern` fn uses type `Struct`, which is not FFI-safe LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this 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..3c62ba7cefcb0 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 this struct + = note: `Foo` has unspecified layout note: the type is defined here --> $DIR/issue-16250.rs:3:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr index b230475df1c6b..cd7a83a350c33 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 this struct + = 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 this struct + = note: `NotSafe` has unspecified layout note: the type is defined here --> $DIR/lint-113436-1.rs:13:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr index df2da2da1e37f..f42924f4d5b56 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr @@ -4,6 +4,12 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | pub fn lint_me() -> A; | ^ not FFI-safe | + = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field +note: the type is defined here + --> $DIR/lint-73249-5.rs:16:1 + | +LL | pub struct A { + | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-73249-5.rs:2:9 diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index f079c2705e7cd..522da2f72576d 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -81,8 +81,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -95,8 +95,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -109,8 +109,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -123,8 +123,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 42d213a05b53d..0b54227d6770a 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -90,6 +90,7 @@ extern "C" { //~^^ ERROR: uses type `char` //~| ERROR: uses type `&dyn Debug` //~| ERROR: uses type `TwoBadTypes<'_>` + //~| ERROR: uses type `TwoBadTypes<'_>` pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index b2bf61461e8f2..4c9c39ef18457 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -63,7 +63,7 @@ LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe | = help: consider adding a member to this struct - = note: this struct has no fields + = note: `ZeroSize` has no fields note: the type is defined here --> $DIR/lint-ctypes.rs:24:1 | @@ -115,6 +115,12 @@ error: `extern` block uses type `TransparentStr`, which is not FFI-safe LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | + = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:32:1 + | +LL | pub struct TransparentStr(&'static str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ = help: consider using `*const u8` and a length instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer @@ -159,8 +165,23 @@ LL | pub struct TwoBadTypes<'a> { = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent +error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:88:59 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:55: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:95:47 + --> $DIR/lint-ctypes.rs:96:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -168,7 +189,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:97:26 + --> $DIR/lint-ctypes.rs:98:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -177,7 +198,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:99:26 + --> $DIR/lint-ctypes.rs:100:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -185,5 +206,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index afd4bccf5aece..247d8950afa2d 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -95,7 +95,7 @@ LL | pub extern "C" fn zero_size(p: ZeroSize) { } | ^^^^^^^^ not FFI-safe | = help: consider adding a member to this struct - = note: this struct has no fields + = note: `ZeroSize` has no fields note: the type is defined here --> $DIR/lint-fn.rs:25:1 | @@ -147,6 +147,12 @@ error: `extern` fn uses type `TransparentStr`, which is not FFI-safe LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe | + = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field +note: the type is defined here + --> $DIR/lint-fn.rs:39:1 + | +LL | pub struct TransparentStr(&'static str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ = help: consider using `*const u8` and a length instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer @@ -164,8 +170,8 @@ error: `extern` fn uses type `Vec`, which is not FFI-safe LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `Vec` has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe --> $DIR/lint-fn.rs:181:41 @@ -173,8 +179,8 @@ error: `extern` fn uses type `Vec`, which is not FFI-safe LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `Vec` has unspecified layout error: aborting due to 18 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-transparent-help.rs b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs new file mode 100644 index 0000000000000..578e45359448a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs @@ -0,0 +1,21 @@ +#![deny(improper_ctypes_definitions)] +use std::marker::PhantomData; +use std::collections::HashMap; +use std::ffi::c_void; + +// [option 1] oops, we forgot repr(C) +struct DictPhantom<'a, A,B:'a>{ + value_info: PhantomData<&'a B>, + full_dict_info: PhantomData>, +} + +#[repr(C)] // [option 2] oops, we meant repr(transparent) +struct MyTypedRawPointer<'a,T:'a>{ + ptr: *const c_void, + metadata: DictPhantom<'a,T,T>, +} + +extern "C" fn example_use(_e: MyTypedRawPointer) {} +//~^ ERROR: uses type `MyTypedRawPointer<'_, i32>` + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr b/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr new file mode 100644 index 0000000000000..b80b808743054 --- /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 this struct + = note: `DictPhantom<'_, i32, i32>` has unspecified layout +note: the type is defined here + --> $DIR/lint-transparent-help.rs:7:1 + | +LL | struct DictPhantom<'a, A,B:'a>{ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint-transparent-help.rs:1:9 + | +LL | #![deny(improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr index 7735f8d09b60c..b763570ef1549 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 this struct + = 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 this struct + = 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 this struct + = 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 this struct + = 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 this struct + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | diff --git a/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr b/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr index a6ccfd2980cdf..81420cd630db3 100644 --- a/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr +++ b/tests/ui/lint/lint-gpu-kernel.amdgpu.stderr @@ -39,8 +39,8 @@ warning: `extern` fn uses type `S`, which is not FFI-safe LL | extern "gpu-kernel" fn arg_struct(_: S) { } | ^ 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 this struct + = note: `S` has unspecified layout note: the type is defined here --> $DIR/lint-gpu-kernel.rs:47:1 | diff --git a/tests/ui/lint/lint-gpu-kernel.nvptx.stderr b/tests/ui/lint/lint-gpu-kernel.nvptx.stderr index a6ccfd2980cdf..81420cd630db3 100644 --- a/tests/ui/lint/lint-gpu-kernel.nvptx.stderr +++ b/tests/ui/lint/lint-gpu-kernel.nvptx.stderr @@ -39,8 +39,8 @@ warning: `extern` fn uses type `S`, which is not FFI-safe LL | extern "gpu-kernel" fn arg_struct(_: S) { } | ^ 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 this struct + = note: `S` has unspecified layout note: the type is defined here --> $DIR/lint-gpu-kernel.rs:47: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..9fb34803f05a6 100644 --- a/tests/ui/union/union-repr-c.stderr +++ b/tests/ui/union/union-repr-c.stderr @@ -4,8 +4,8 @@ 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 - = note: this union has unspecified layout + = help: consider adding a `#[repr(C)]` attribute to this union + = note: `W` has unspecified layout note: the type is defined here --> $DIR/union-repr-c.rs:9:1 | From 9f84f57f65a0761387b03ad5fb296d6a10aedb3b Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:18:16 +0200 Subject: [PATCH 08/17] 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, --- .../rustc_lint/src/types/improper_ctypes.rs | 42 +++++++++++++++---- .../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 +- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 4f2dcb1b1f95f..51751b9c747f4 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -662,7 +662,7 @@ enum OuterTyKind { }, /// For struct/enum/union fields AdtField, - /// Placeholder for properties that will be used eventually + /// For arrays/slices but also tuples Other, } @@ -750,6 +750,15 @@ impl VisitorState { } } + /// Whether the type is used as the type of a static variable. + fn is_direct_in_static(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::STATIC); + if ret { + debug_assert!(!self.root_use_flags.contains(RootUseFlags::FUNC)); + } + ret && matches!(self.outer_ty_kind, OuterTyKind::None) + } + /// Whether the type is used in a function. fn is_in_function(&self) -> bool { let ret = self.root_use_flags.contains(RootUseFlags::FUNC); @@ -795,6 +804,11 @@ impl VisitorState { fn is_raw_pointee(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::Pointee { raw: true, .. }) } + + /// Whether the current type directly in the memory layout of the parent ty + fn is_memory_inlined(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::AdtField | OuterTyKind::Other) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -927,7 +941,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { TypeSizedness::MetaSized => { let help = match inner_ty.kind() { ty::Str => Some(msg!("consider using `*const u8` and a length instead")), - ty::Slice(_) => Some(msg!("consider using a raw pointer instead")), + ty::Slice(_) => Some(msg!( + "consider using a raw pointer to the slice's first element (and a length) instead" + )), _ => None, }; let reason = match indirection_kind { @@ -1278,11 +1294,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Some(msg!("consider using `u32` or `libc::wchar_t` instead")), ), - ty::Slice(_) => FfiResult::new_with_reason( - ty, - msg!("slices have no C equivalent"), - Some(msg!("consider using a raw pointer instead")), - ), + 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.next(ty), inner_ty) + } else { + FfiResult::new_with_reason( + ty, + msg!("slices have no C equivalent"), + Some(msg!( + "consider using a raw pointer to the slice's first element (and a length) instead" + )), + ) + } + } ty::Dynamic(..) => { FfiResult::new_with_reason(ty, msg!("trait objects have no C equivalent"), None) diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index f0c0cc8167fd0..02dad17490006 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -4,7 +4,7 @@ error: `extern` callback uses type `&[u8]`, which is not FFI-safe LL | pub type F = extern "C" fn(&[u8]); | ^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index 522da2f72576d..a1e81a2929e7a 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -4,7 +4,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent note: the lint level is defined here --> $DIR/lint-94223.rs:2:38 @@ -18,7 +18,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -27,7 +27,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -36,7 +36,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | struct BadStruct(extern "C" fn([u8])); | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -45,7 +45,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -54,7 +54,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -63,7 +63,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | type Foo = extern "C" fn([u8]); | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 4c9c39ef18457..8fccfbb8a6f18 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `&[u32]`, which is not FFI-safe LL | pub fn slice_type(p: &[u32]); | ^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-ctypes.rs:5:9 @@ -177,7 +177,7 @@ note: the type is defined here | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 247d8950afa2d..6b5dc80bb9af9 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -18,7 +18,7 @@ error: `extern` fn uses type `&[u32]`, which is not FFI-safe LL | pub extern "C" fn slice_type(p: &[u32]) { } | ^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-fn.rs:2:26 @@ -41,7 +41,7 @@ error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box`, which is not FFI-safe From 7ee564f286e1e4ff16ee8ed9ad1a2d37ff23db0b Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:35:24 +0200 Subject: [PATCH 09/17] 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. --- .../rustc_lint/src/types/improper_ctypes.rs | 164 ++++++++++++++---- tests/ui/lint/improper-ctypes/lint-enum.rs | 2 +- .../ui/lint/improper-ctypes/lint-enum.stderr | 25 ++- .../lint/improper-ctypes/lint_uninhabited.rs | 74 ++++++++ .../improper-ctypes/lint_uninhabited.stderr | 121 +++++++++++++ tests/ui/structs-enums/foreign-struct.rs | 15 +- 6 files changed, 353 insertions(+), 48 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/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 51751b9c747f4..0129b1adb21ed 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -14,7 +14,7 @@ use rustc_middle::ty::{ TypeVisitable, TypeVisitableExt, Unnormalized, }; use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{LocalDefId, LocalModId}; use rustc_span::{Span, sym}; use rustc_target::spec::Os; use tracing::debug; @@ -370,7 +370,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) => { @@ -816,14 +815,34 @@ impl VisitorState { /// and ``visit_*`` methods to recurse. struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, + /// The module id of the item being checked for FFI-safety + mod_id: LocalModId, /// To prevent problems with recursive types, /// add a types-in-check cache. ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - ImproperCTypesVisitor { cx, ty_cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>, mod_id: LocalModId) -> Self { + ImproperCTypesVisitor { cx, mod_id, ty_cache: FxHashSet::default() } + } + + /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here. + fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + if state.is_in_function_return() { + FfiResult::FfiSafe + } else { + let desc = match ty.kind() { + ty::Adt(..) => msg!( + "zero-variant enums and other uninhabited types are not allowed in function arguments and static variables" + ), + ty::Never => msg!( + "the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables" + ), + 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. @@ -974,8 +993,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { use FfiResult::*; // The decision tree for the safety of a list of fields is as follows: + // (but please note that the conditionals are not evaluated in that order) + // // - is it neither `repr(C)`, `transparent` (for a struct), nor `repr(int_type)` (for enums)? // - if so, it is unsafe. + // - are we in a situation where uninhabitedness is an issue? + // - if so, raise lints for all uninhabited fields // - are all the fields PhantomData? // - if so, the struct as a whole is PhantomData // - is it a transparent struct? @@ -988,21 +1011,52 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { 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::>()) - } + 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 !ty.is_inhabited_from(self.cx.tcx, self.mod_id, self.cx.typing_env()) { + // `repr(transparent)` structs are FFI-safe when some of their 1ZSTs are uninhabited + // and if we are in a context where uninhabitedness is allowed (function returns, etc) + // Notably, transparent structs with a data type and an uninhabited 1ZST marker + // is what models `[[noreturn]]` C functions with a possibly non-void return type, + // which still requires things like stack allocations prior to the call. + // see https://github.com/rust-lang/rust/pull/134697#issuecomment-2937936422 + // + // However, if we are in a context where uninhabitedness is forbidden (function argument, etc), + // we must make sure that we lint on all uninhabited fields, even if we discard + // all other sources of FFI-unsafety from them. + ffires_accumulator += variant + .fields + .iter() + .map(|field| { + let field_ty = maybe_normalize_erasing_regions( + self.cx, + field.ty(self.cx.tcx, args), + ); + let mut field_res = self.visit_type(state.next(ty), field_ty); + field_res.take_with_core_note(&[ + msg!("zero-variant enums and other uninhabited types are not allowed in function arguments and static variables"), + msg!("the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables"), + ]) + }) + .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]) } else { - (false, variant.fields.iter().collect::>()) - }; + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + (true, variant.fields.iter().collect::>()) + } + } else { + (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(); @@ -1161,8 +1215,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. @@ -1216,11 +1270,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else if nonexhaustive_variant_flag { FfiResult::new_with_reason(ty, msg!("this enum has non-exhaustive variants"), 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(&[ + msg!("zero-variant enums and other uninhabited types are not allowed in function arguments and static variables"), + msg!("the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables"), + ]); // 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() @@ -1228,6 +1292,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``) @@ -1397,7 +1466,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. @@ -1504,6 +1573,7 @@ impl<'tcx> ImproperCTypesLint { current_depth: usize, depths: Vec, decls: Vec<&'tcx hir::FnDecl<'tcx>>, + hir_ids: Vec, tys: Vec>, } @@ -1516,6 +1586,7 @@ impl<'tcx> ImproperCTypesLint { { self.decls.push(*decl); self.depths.push(self.current_depth); + self.hir_ids.push(ty.hir_id); } hir::intravisit::walk_ty(self, ty); @@ -1538,6 +1609,7 @@ impl<'tcx> ImproperCTypesLint { } let mut visitor = FnPtrFinder { + hir_ids: Vec::new(), tys: Vec::new(), decls: Vec::new(), depths: Vec::new(), @@ -1547,15 +1619,24 @@ impl<'tcx> ImproperCTypesLint { visitor.visit_ty_unambig(hir_ty); let all_types = iter::zip( - visitor.depths.drain(..), + iter::zip(visitor.depths.drain(..), visitor.hir_ids.drain(..)), iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)), ); - for (depth, (fn_ptr_ty, decl)) in all_types { + + for ((depth, hir_id), (fn_ptr_ty, decl)) in all_types { let sig = get_sig_from_fnptr_ty(fn_ptr_ty); + let mod_id = cx.tcx.parent_module(hir_id); // FIXME: does this cause a double normalisation? (since this signature comes from // the normalised `ty` argument of this method) Is this a performance problem? - self.check_foreign_fn(cx, CItemKind::Callback, Unnormalized::new_wip(sig), decl, depth); + self.check_foreign_fn( + cx, + CItemKind::Callback, + Unnormalized::new_wip(sig), + decl, + mod_id, + depth, + ); } } @@ -1597,9 +1678,10 @@ impl<'tcx> ImproperCTypesLint { } /// Check that an extern "ABI" static variable is of a ffi-safe type. - fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { - let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx); + fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(VisitorState::static_entry_point(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1611,6 +1693,7 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, sig: Unnormalized<'tcx, Sig<'tcx>>, decl: &'tcx hir::FnDecl<'_>, + mod_id: LocalModId, depth: usize, ) { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig.skip_norm_wip()); @@ -1618,7 +1701,7 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, Unnormalized::new_wip(*input_ty)); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } @@ -1626,7 +1709,7 @@ impl<'tcx> ImproperCTypesLint { if let hir::FnRetTy::Return(ret_hir) = decl.output { let mut state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, Unnormalized::new_wip(sig.output())); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -1754,12 +1837,20 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // their surroundings, and their type is often declared inline self.check_fn_for_external_abi_fnptr(cx, it.owner_id.def_id, hir_sig.decl); let sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(it.owner_id.def_id); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ImportedExtern, sig, hir_sig.decl, 0); + self.check_foreign_fn( + cx, + CItemKind::ImportedExtern, + sig, + hir_sig.decl, + mod_id, + 0, + ); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { - self.check_foreign_static(cx, it.owner_id, ty.span); + self.check_foreign_static(cx, it.hir_id(), ty.span); } hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), } @@ -1830,9 +1921,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline self.check_fn_for_external_abi_fnptr(cx, id, decl); - let sig = cx.tcx.fn_sig(id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ExportedFunction, sig, decl, 0); + let sig = cx.tcx.fn_sig(id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(id); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, sig, decl, mod_id, 0); } } } diff --git a/tests/ui/lint/improper-ctypes/lint-enum.rs b/tests/ui/lint/improper-ctypes/lint-enum.rs index f900f998d06cb..2a69275702c6f 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.rs +++ b/tests/ui/lint/improper-ctypes/lint-enum.rs @@ -78,7 +78,7 @@ struct Field(()); enum NonExhaustive {} extern "C" { - fn zf(x: Z); + fn zf(x: Z); //~ ERROR `extern` block uses type `Z` fn uf(x: U); //~ ERROR `extern` block uses type `U` fn bf(x: B); //~ ERROR `extern` block uses type `B` fn tf(x: T); //~ ERROR `extern` block uses type `T` diff --git a/tests/ui/lint/improper-ctypes/lint-enum.stderr b/tests/ui/lint/improper-ctypes/lint-enum.stderr index 35d1dcb87fd82..25a05d2c75089 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.stderr +++ b/tests/ui/lint/improper-ctypes/lint-enum.stderr @@ -1,3 +1,21 @@ +error: `extern` block uses type `Z`, which is not FFI-safe + --> $DIR/lint-enum.rs:81:14 + | +LL | fn zf(x: Z); + | ^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint-enum.rs:8:1 + | +LL | enum Z {} + | ^^^^^^ +note: the lint level is defined here + --> $DIR/lint-enum.rs:2:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + error: `extern` block uses type `U`, which is not FFI-safe --> $DIR/lint-enum.rs:82:14 | @@ -11,11 +29,6 @@ note: the type is defined here | LL | enum U { | ^^^^^^ -note: the lint level is defined here - --> $DIR/lint-enum.rs:2:9 - | -LL | #![deny(improper_ctypes)] - | ^^^^^^^^^^^^^^^ error: `extern` block uses type `B`, which is not FFI-safe --> $DIR/lint-enum.rs:83:14 @@ -207,5 +220,5 @@ LL | fn result_unit_t_e(x: Result<(), ()>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: aborting due to 22 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.rs b/tests/ui/lint/improper-ctypes/lint_uninhabited.rs new file mode 100644 index 0000000000000..32cbb09644426 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint_uninhabited.rs @@ -0,0 +1,74 @@ +#![feature(never_type)] + +#![allow(dead_code, unused_variables)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::mem::transmute; + +enum Uninhabited{} + +#[repr(C)] +struct AlsoUninhabited{ + a: Uninhabited, + b: i32, +} + +#[repr(C)] +enum Inhabited{ + OhNo(Uninhabited), + OhYes(i32), +} + +struct EmptyRust; + +#[repr(transparent)] +struct HalfHiddenUninhabited { + is_this_a_tuple: (i8,i8), + zst_inh: EmptyRust, + zst_uninh: !, +} + +extern "C" { + +fn bad_entry(e: AlsoUninhabited); //~ ERROR: uses type `AlsoUninhabited` +fn bad_exit()->AlsoUninhabited; + +fn bad0_entry(e: Uninhabited); //~ ERROR: uses type `Uninhabited` +fn bad0_exit()->Uninhabited; + +fn good_entry(e: Inhabited); +fn good_exit()->Inhabited; + +fn never_entry(e:!); //~ ERROR: uses type `!` +fn never_exit()->!; + +} + +extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} //~ ERROR: uses type `AlsoUninhabited` +extern "C" fn impl_bad_exit()->AlsoUninhabited { + AlsoUninhabited{ + a: impl_bad0_exit(), + b: 0, + } +} + +extern "C" fn impl_bad0_entry(e: Uninhabited) {} //~ ERROR: uses type `Uninhabited` +extern "C" fn impl_bad0_exit()->Uninhabited { + unsafe{transmute(())} //~ WARN: does not permit zero-initialization +} + +extern "C" fn impl_good_entry(e: Inhabited) {} +extern "C" fn impl_good_exit() -> Inhabited { + Inhabited::OhYes(0) +} + +extern "C" fn impl_never_entry(e:!){} //~ ERROR: uses type `!` +extern "C" fn impl_never_exit()->! { + loop{} +} + +extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} +//~^ ERROR: uses type `HalfHiddenUninhabited` + + +fn main(){} diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr b/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr new file mode 100644 index 0000000000000..9488dd0f62378 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr @@ -0,0 +1,121 @@ +error: `extern` block uses type `AlsoUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:33:17 + | +LL | fn bad_entry(e: AlsoUninhabited); + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:11:1 + | +LL | struct AlsoUninhabited{ + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:4:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:36:18 + | +LL | fn bad0_entry(e: Uninhabited); + | ^^^^^^^^^^^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +error: `extern` block uses type `!`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:42:18 + | +LL | fn never_entry(e:!); + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `AlsoUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:47:33 + | +LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:11:1 + | +LL | struct AlsoUninhabited{ + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:4:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:55:34 + | +LL | extern "C" fn impl_bad0_entry(e: Uninhabited) {} + | ^^^^^^^^^^^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +warning: the type `Uninhabited` does not permit zero-initialization + --> $DIR/lint_uninhabited.rs:57:12 + | +LL | unsafe{transmute(())} + | ^^^^^^^^^^^^^ this code causes undefined behavior when executed + | +note: enums with no inhabited variants have no valid value + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + = note: `#[warn(invalid_value)]` on by default + +error: `extern` fn uses type `!`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:65:34 + | +LL | extern "C" fn impl_never_entry(e:!){} + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `HalfHiddenUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:70:31 + | +LL | extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`HalfHiddenUninhabited`) is FFI-unsafe due to a `!` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:25:1 + | +LL | struct HalfHiddenUninhabited { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: aborting due to 7 previous errors; 1 warning emitted + diff --git a/tests/ui/structs-enums/foreign-struct.rs b/tests/ui/structs-enums/foreign-struct.rs index b710d83350abf..126aba880cb20 100644 --- a/tests/ui/structs-enums/foreign-struct.rs +++ b/tests/ui/structs-enums/foreign-struct.rs @@ -1,17 +1,22 @@ //@ check-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 1085d72b64b09a8951ba591403dfe5c7a4fd0fdc Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:45:19 +0200 Subject: [PATCH 10/17] ImproperCTypes: handle the Option case Properly treat the fact that a pattern can create assumptions that are used by Option-like enums to be smaller, making those enums FFI-safe without `[repr(C)]`. --- compiler/rustc_lint/src/types.rs | 159 +++++++++++++++++- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 5 + .../lint/improper-ctypes/lint-ctypes.stderr | 63 ++++--- 3 files changed, 197 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 14b8bbbee21fd..aeb27ff8a564f 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1,12 +1,12 @@ use std::iter; -use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; +use rustc_abi::{BackendRepr, Size, TagEncoding, Variants, WrappingRange}; use rustc_ast as ast; use rustc_hir as hir; use rustc_hir::{Expr, ExprKind, HirId, LangItem, find_attr}; use rustc_middle::bug; use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; +use rustc_middle::ty::{self, Const, ScalarInt, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::debug; @@ -878,7 +878,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>, @@ -907,6 +907,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; } @@ -953,6 +961,151 @@ pub(crate) fn repr_nullable_ptr<'tcx>( } } +/// Returns whether a pattern type actually has disallowed values. +pub(crate) fn pattern_has_disallowed_values<'tcx>(pat: ty::Pattern<'tcx>) -> bool { + // note the logic in this function assumes that signed ints use one's complement representation, + // which I believe is a requirement for rust + + /// Find numeric metadata on a pair of range bounds. + /// If None, assume that there are no bounds specified + /// and that this is a usize. in other words, all values are allowed. + fn unwrap_start_end<'tcx>( + start: Const<'tcx>, + end: Const<'tcx>, + ) -> (bool, Size, ScalarInt, ScalarInt) { + let usable_bound = match (start.try_to_value(), end.try_to_value()) { + (Some(ty), _) | (_, Some(ty)) => ty, + (None, None) => bug!( + "pattern range should have at least one defined value: {:?} - {:?}", + start, + end, + ), + }; + let usable_size = usable_bound.valtree.to_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.to_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.to_leaf(), + None => { + let min_val = if is_signed { + (usable_size.signed_int_min() as u128) & usable_size.unsigned_int_max() + } else { + 0_u128 + }; + ScalarInt::try_from_uint(min_val, usable_size).unwrap() + } + }; + (is_signed, usable_size, start, end) + } + + match *pat { + ty::PatternKind::NotNull => true, + ty::PatternKind::Range { start, end } => { + let (is_signed, scalar_size, start, end) = unwrap_start_end(start, end); + let (scalar_min, scalar_max) = if is_signed { + ( + (scalar_size.signed_int_min() as u128) & scalar_size.unsigned_int_max(), + scalar_size.signed_int_max() as u128, + ) + } else { + (0, scalar_size.unsigned_int_max()) + }; + + (start.to_bits(scalar_size), end.to_bits(scalar_size)) != (scalar_min, scalar_max) + } + ty::PatternKind::Or(patterns) => { + // first, get a simplified an sorted view of the ranges + let (is_signed, scalar_size, mut ranges) = { + let (is_signed, size, start, end) = match &*patterns[0] { + ty::PatternKind::Range { start, end } => unwrap_start_end(*start, *end), + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + }; + (is_signed, size, vec![(start, end)]) + }; + let scalar_max = if is_signed { + scalar_size.signed_int_max() as u128 + } else { + scalar_size.unsigned_int_max() + }; + ranges.reserve(patterns.len() - 1); + for pat in patterns.iter().skip(1) { + match *pat { + ty::PatternKind::Range { start, end } => { + let (is_this_signed, this_scalar_size, start, end) = + unwrap_start_end(start, end); + assert_eq!(is_signed, is_this_signed); + assert_eq!(scalar_size, this_scalar_size); + ranges.push((start, end)) + } + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + } + } + ranges.sort_by_key(|(start, _end)| { + let is_positive = + if is_signed { start.to_bits(scalar_size) <= scalar_max } else { true }; + (is_positive, start.to_bits(scalar_size)) + }); + + // then, range per range, look at the sizes of the gaps left in between + // (`prev_tail` is the highest value currently accounted for by the ranges, + // unless the first range has not been dealt with yet) + let mut prev_tail = scalar_max; + + for (range_i, (start, end)) in ranges.into_iter().enumerate() { + let (start, end) = (start.to_bits(scalar_size), end.to_bits(scalar_size)); + + // if the start of the current range is lower + // than the current-highest-range-end, ... + let current_range_overlap = + if is_signed && prev_tail > scalar_max && start <= scalar_max { + false + } else if start <= u128::overflowing_add(prev_tail, 1).0 { + range_i > 0 // no overlap possible when dealing with the first range + } else { + false + }; + if current_range_overlap { + // update the current-highest-range-end, if the current range has a higher end + if is_signed { + if prev_tail > scalar_max && end <= scalar_max { + prev_tail = end; + } else if prev_tail <= scalar_max && end > scalar_max { + // nothing to do here + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // no range overlap: there are disallowed values + return true; + } + } + prev_tail != scalar_max + } + } +} + fn get_nullable_type_from_pat<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 0b54227d6770a..2b0f5b367e76a 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,5 +1,7 @@ #![feature(rustc_private)] #![feature(extern_types)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -8,6 +10,7 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; use std::fmt::Debug; +use std::pat::pattern_type; unsafe extern "C" {type UnsizedOpaque;} trait Bar { } @@ -70,6 +73,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 8fccfbb8a6f18..49eb06324560f 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:68:26 + --> $DIR/lint-ctypes.rs:71:26 | LL | pub fn slice_type(p: &[u32]); | ^^^^^^ not FFI-safe @@ -7,13 +7,13 @@ LL | pub fn slice_type(p: &[u32]); = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here - --> $DIR/lint-ctypes.rs:5:9 + --> $DIR/lint-ctypes.rs:7:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:69:24 + --> $DIR/lint-ctypes.rs:72: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:72:25 + --> $DIR/lint-ctypes.rs:75: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:76: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:73:26 + --> $DIR/lint-ctypes.rs:78: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:74:26 + --> $DIR/lint-ctypes.rs:79: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:75:27 + --> $DIR/lint-ctypes.rs:80: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:76:25 + --> $DIR/lint-ctypes.rs:81:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -65,26 +74,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: `ZeroSize` has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:24:1 + --> $DIR/lint-ctypes.rs:27:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:77:33 + --> $DIR/lint-ctypes.rs:82: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:61: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:80:12 + --> $DIR/lint-ctypes.rs:85: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:81:23 + --> $DIR/lint-ctypes.rs:86: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 a Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:82:24 + --> $DIR/lint-ctypes.rs:87: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 a Rust-specific calling convention error: `extern` block uses type `TransparentStr`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:84:31 + --> $DIR/lint-ctypes.rs:89:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field note: the type is defined here - --> $DIR/lint-ctypes.rs:32:1 + --> $DIR/lint-ctypes.rs:35:1 | LL | pub struct TransparentStr(&'static str); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -125,7 +134,7 @@ LL | pub struct TransparentStr(&'static str); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:86:27 + --> $DIR/lint-ctypes.rs:91: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:88:36 + --> $DIR/lint-ctypes.rs:93:36 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^ not FFI-safe @@ -143,7 +152,7 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = note: the `char` type has no C equivalent error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:88:44 + --> $DIR/lint-ctypes.rs:93:44 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^ not FFI-safe @@ -151,14 +160,14 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:88:59 + --> $DIR/lint-ctypes.rs:93:59 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `char` field note: the type is defined here - --> $DIR/lint-ctypes.rs:55:1 + --> $DIR/lint-ctypes.rs:58:1 | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -166,14 +175,14 @@ LL | pub struct TwoBadTypes<'a> { = note: the `char` type has no C equivalent error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:88:59 + --> $DIR/lint-ctypes.rs:93:59 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field note: the type is defined here - --> $DIR/lint-ctypes.rs:55:1 + --> $DIR/lint-ctypes.rs:58:1 | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +190,7 @@ LL | pub struct TwoBadTypes<'a> { = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:96:47 + --> $DIR/lint-ctypes.rs:101:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -189,7 +198,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:98:26 + --> $DIR/lint-ctypes.rs:103:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -198,7 +207,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:100:26 + --> $DIR/lint-ctypes.rs:105:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -206,5 +215,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 20 previous errors +error: aborting due to 21 previous errors From 05e46d9b9198a032535963432f3af377c1de1fae Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:47:02 +0200 Subject: [PATCH 11/17] 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 | 120 ++++++++++++------ 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 0129b1adb21ed..06daadf3a0589 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -166,25 +166,39 @@ fn get_sig_from_fnptr_ty<'tcx>(ty: Ty<'tcx>) -> Sig<'tcx> { } } -/// A common pattern in this lint is to attempt normalize_erasing_regions, -/// but keep the original type if it were to fail. -/// This may or may not be supported in the logic behind the `Unnormalized` wrapper, -/// (FIXME?) -/// but it should be enough for non-wrapped types to be as normalised as this lint needs them to be. +// FIXME(ctypes): it seems that tests/ui/lint/opaque-ty-ffi-normalization-cycle.rs relies on +// the fact that we consider opaque aliases that normalise to something else to be unsafe. +// ...is it the behaviour we want? +// possible FIXME(ctypes,normalization): this maybe-normalised output may or may not be supported in the logic +// behind the `Unnormalized` wrapper, but it should be enough for non-wrapped types to +// be as normalised as this lint needs them to be. +/// a modified version of cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty.skip_normalization()) +/// so that opaque types prevent normalisation once region erasure occurs fn maybe_normalize_erasing_regions<'tcx>( cx: &LateContext<'tcx>, value: Unnormalized<'tcx, Ty<'tcx>>, ) -> Ty<'tcx> { - // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaque types since we're now - // past hir typeck. If we were to attempt to reveal more opaque types, dropping the - // `InferCtxt` would ICE (see #156352). - let typing_env = if let Some(body_id) = cx.enclosing_body { - let body_def_id = cx.tcx.hir_enclosing_body_owner(body_id.hir_id); - ty::TypingEnv::new(cx.param_env, ty::TypingMode::borrowck(cx.tcx, body_def_id)) + let value_inner = value.skip_norm_wip(); + if (!value_inner.has_aliases()) || value_inner.has_opaque_types() { + cx.tcx.erase_and_anonymize_regions(value_inner) } else { - cx.typing_env() - }; - cx.tcx.try_normalize_erasing_regions(typing_env, value).unwrap_or(value.skip_norm_wip()) + // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaque types since we're now + // past hir typeck. If we were to attempt to reveal more opaque types, dropping the + // `InferCtxt` would ICE (see #156352). + let typing_env = if let Some(body_id) = cx.enclosing_body { + let body_def_id = cx.tcx.hir_enclosing_body_owner(body_id.hir_id); + ty::TypingEnv::new(cx.param_env, ty::TypingMode::borrowck(cx.tcx, body_def_id)) + } else { + cx.typing_env() + }; + + cx.tcx.try_normalize_erasing_regions(typing_env, value).unwrap_or(value_inner) + // note: the code above ^^^ should only cause a call to the commented code below vvv + //let value = value.skip_normalization(); + //let value = cx.tcx.erase_and_anonymize_regions(value); + //let mut folder = TryNormalizeAfterErasingRegionsFolder::new(cx.tcx, typing_env); + //value.try_fold_with(&mut folder).unwrap_or(value) + } } fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { @@ -1468,19 +1482,64 @@ 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::AliasTy { kind: ty::Opaque { .. }, .. }) => { FfiResult::new_with_reason(ty, msg!("opaque types have no C equivalent"), 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. + // 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::AliasTy { kind: ty::Projection { .. } | ty::Inherent { .. }, .. }) - if state.can_expect_ty_params() => - { - FfiSafe + | ty::Alias( + _, + ty::AliasTy { + kind: 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 maybe_normalize_erasing_regions, + // normalisation has already occurred + debug_assert_eq!( + self.cx + .tcx + .try_normalize_erasing_regions( + self.cx.typing_env(), + Unnormalized::new_wip(ty) + ) + .unwrap_or(ty), + ty, + ); + + if matches!( + ty.kind(), + ty::Param(..) + | ty::Alias( + _, + ty::AliasTy { + kind: 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(_) => FfiResult::new_with_reason( @@ -1502,19 +1561,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { None, ), - ty::Param(..) - | ty::Alias( - _, - ty::AliasTy { - kind: ty::Projection { .. } | ty::Inherent { .. } | ty::Free { .. }, - .. - }, - ) - | ty::Infer(..) - | ty::Bound(..) - | ty::Error(_) - | ty::Placeholder(..) - | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + ty::Infer(..) | ty::Bound(..) | ty::Error(_) | ty::Placeholder(..) | ty::FnDef(..) => { + bug!("unexpected type in foreign function: {:?}", ty) + } } } @@ -1553,9 +1602,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return res; } let ty = maybe_normalize_erasing_regions(self.cx, ty); - if let Some(res) = self.visit_for_opaque_ty(ty) { - return res; - } self.visit_type(state, ty) } } From 771a21b6d88a37a31e8c562a3ef5d5bbeaa32b97 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:50:56 +0200 Subject: [PATCH 12/17] 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 | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 06daadf3a0589..cd3abb055dd26 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -1973,4 +1973,77 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { self.check_foreign_fn(cx, CItemKind::ExportedFunction, sig, decl, mod_id, 0); } } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, tr_it: &hir::TraitItem<'tcx>) { + match tr_it.kind { + hir::TraitItemKind::Const(hir_ty, _) => { + let ty = cx + .tcx + .type_of(hir_ty.hir_id.owner.def_id) + .instantiate_identity() + .skip_norm_wip(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + hir::TraitItemKind::Fn(sig, trait_fn) => { + match trait_fn { + // if the method is defined here, + // there is a matching ``LateLintPass::check_fn`` call, + // let's not redo that work + hir::TraitFn::Provided(_) => return, + hir::TraitFn::Required(_) => (), + } + let local_id = tr_it.owner_id.def_id; + + self.check_fn_for_external_abi_fnptr(cx, local_id, sig.decl); + if !sig.header.abi.is_rustic_abi() { + let mir_sig = cx.tcx.fn_sig(local_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(local_id); + self.check_foreign_fn( + cx, + CItemKind::ExportedFunction, + mir_sig, + sig.decl, + mod_id, + 0, + ); + } + } + hir::TraitItemKind::Type(_, ty_maybe) => { + if let Some(hir_ty) = ty_maybe { + let ty = cx + .tcx + .type_of(hir_ty.hir_id.owner.def_id) + .instantiate_identity() + .skip_norm_wip(); + 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() + .skip_norm_wip(); + 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() + .skip_norm_wip(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + } + } } From 1c17bbe3f1a51b4aa6f97f873718f7d3ed76a869 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:55:54 +0200 Subject: [PATCH 13/17] ImproperCTypes: also check 'exported' static variables Added the missing case for FFI-exposed pieces of code: static variables with the `no_mangle` or `export_name` annotations. This adds a new lint, which is managed by the rest of the ImproperCTypes architecture. --- .../rustc_lint/src/types/improper_ctypes.rs | 57 ++++++++++++++++--- .../exported_symbol_wrong_type.rs | 1 + tests/ui/lint/improper-ctypes/lint-ctypes.rs | 11 +++- .../lint/improper-ctypes/lint-ctypes.stderr | 18 +++++- tests/ui/lint/runtime-symbols-no-std.rs | 2 +- tests/ui/lint/runtime-symbols-unix.rs | 1 + tests/ui/lint/runtime-symbols-unix.stderr | 22 +++---- tests/ui/lint/runtime-symbols.rs | 1 + tests/ui/lint/runtime-symbols.stderr | 24 ++++---- ...-allocations-dont-inherit-codegen-attrs.rs | 2 + 10 files changed, 104 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index cd3abb055dd26..e730bca5c49ec 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -7,7 +7,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{DiagMessage, msg}; use rustc_hir::def::CtorKind; use rustc_hir::intravisit::VisitorExt; -use rustc_hir::{self as hir, AmbigArg}; +use rustc_hir::{self as hir, AmbigArg, find_attr}; use rustc_middle::bug; use rustc_middle::ty::{ self, Adt, AdtDef, AdtKind, Binder, FnSig, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, @@ -58,27 +58,32 @@ declare_lint! { declare_lint! { /// The `improper_ctypes_definitions` lint detects incorrect use of - /// [`extern` function] definitions. - /// (In other words, functions to be used by foreign code.) + /// [`extern` function] definitions and [`no_mangle`] / [`export_name`] static variable definitions. + /// (In other words, functions and global variables to be used by foreign code.) /// /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier + /// [`no_mangle`]: https://doc.rust-lang.org/stable/reference/abi.html#the-no_mangle-attribute + /// [`export_name`]: https://doc.rust-lang.org/stable/reference/abi.html#the-export_name-attribute /// /// ### Example /// /// ```rust /// # #![allow(unused)] /// pub extern "C" fn str_type(p: &str) { } + /// # #[used] + /// # #[unsafe(no_mangle)] + /// static PLUGIN_ABI_MIN_VERSION: &'static str = "0.0.5"; /// ``` /// /// {{produces}} /// /// ### Explanation /// - /// There are many parameter and return types that may be specified in an - /// `extern` function that are not compatible with the given ABI. This - /// lint is an alert that these types should not be used. The lint usually - /// should provide a description of the issue, along with possibly a hint - /// on how to resolve it. + /// There are many types that may be specified at interfaces exposed to foreign code, + /// but are not follow the rules to ensure proper ABI compatibility. + /// This lint is issued when a mistake is detected. + /// The lint usually should provide a description of the issue, + /// along with possibly a hint on how to resolve it. pub(crate) IMPROPER_CTYPES_DEFINITIONS, Warn, "proper use of libc types in foreign item definitions" @@ -289,6 +294,9 @@ enum CItemKind { ExportedFunction, /// `extern "C"` function pointers -> also IMPROPER_CTYPES, Callback, + /// `no_mangle`/`export_name` static variables, assumed to be used from across an FFI boundary, + /// -> also IMPROPER_CTYPES_DEFINITIONS + ExportedStatic, } /// Annotates whether we are in the context of a function's argument types or return type. @@ -715,6 +723,8 @@ struct VisitorState { impl RootUseFlags { // The values that can be set. const STATIC_TY: Self = Self::STATIC; + const EXPORTED_STATIC_TY: Self = + Self::from_bits(Self::STATIC.bits() | Self::DEFINED.bits()).unwrap(); const ARGUMENT_TY_IN_DEFINITION: Self = Self::from_bits(Self::FUNC.bits() | Self::DEFINED.bits()).unwrap(); const RETURN_TY_IN_DEFINITION: Self = @@ -750,6 +760,9 @@ impl VisitorState { (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, (CItemKind::Callback, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_FNPTR, + (CItemKind::ExportedStatic, _) => bug!( + "VisitorState::entry_point_from_fnmode() should not be used for static variables!" + ), }; VisitorState { root_use_flags: p_flags, outer_ty_kind: OuterTyKind::None, depth: 0 } } @@ -763,6 +776,15 @@ impl VisitorState { } } + /// Get the proper visitor state for a locally-defined static variable's type + fn static_def_entry_point() -> Self { + VisitorState { + root_use_flags: RootUseFlags::EXPORTED_STATIC_TY, + outer_ty_kind: OuterTyKind::None, + depth: 0, + } + } + /// Whether the type is used as the type of a static variable. fn is_direct_in_static(&self) -> bool { let ret = self.root_use_flags.contains(RootUseFlags::STATIC); @@ -1732,6 +1754,15 @@ impl<'tcx> ImproperCTypesLint { self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } + /// Check that a `#[no_mangle]`/`#[export_name = _]` static variable is of a ffi-safe type. + fn check_exported_static(&self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); + let ffi_res = visitor.check_type(VisitorState::static_def_entry_point(), 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, @@ -1838,10 +1869,13 @@ impl<'tcx> ImproperCTypesLint { // Internally, we treat this differently, but at the end of the day // their linting needs to be enabled/disabled alongside that of "FFI-imported" items. CItemKind::Callback => IMPROPER_CTYPES, + // Same thing with static variables, which are "FFI-exported" + CItemKind::ExportedStatic => IMPROPER_CTYPES_DEFINITIONS, }; let desc = match fn_mode { CItemKind::ImportedExtern => "`extern` block", CItemKind::ExportedFunction => "`extern` fn", + CItemKind::ExportedStatic => "foreign-code-reachable static", CItemKind::Callback => "`extern` callback", }; for reason in reasons.iter_mut() { @@ -1912,6 +1946,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { ty, cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(), ); + + if matches!(item.kind, hir::ItemKind::Static(..)) + && (find_attr!(cx.tcx, item.owner_id, NoMangle(_)) + || find_attr!(cx.tcx, item.owner_id, ExportName { .. })) + { + self.check_exported_static(cx, item.hir_id(), ty.span); + } } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks hir::ItemKind::Fn { .. } => {} 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 2b0f5b367e76a..5cc8cf9ac9658 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -4,7 +4,7 @@ #![feature(pattern_type_macro)] #![allow(private_interfaces)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] use std::cell::UnsafeCell; use std::marker::PhantomData; @@ -135,6 +135,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 49eb06324560f..25f52632c6f63 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -9,7 +9,7 @@ LL | pub fn slice_type(p: &[u32]); note: the lint level is defined here --> $DIR/lint-ctypes.rs:7:9 | -LL | #![deny(improper_ctypes)] +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `&str`, which is not FFI-safe @@ -215,5 +215,19 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: foreign-code-reachable static uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:146:29 + | +LL | static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-ctypes.rs:7:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 22 previous errors diff --git a/tests/ui/lint/runtime-symbols-no-std.rs b/tests/ui/lint/runtime-symbols-no-std.rs index cef75e45b02d3..e821d05b7494d 100644 --- a/tests/ui/lint/runtime-symbols-no-std.rs +++ b/tests/ui/lint/runtime-symbols-no-std.rs @@ -17,7 +17,7 @@ extern "C" { } #[no_mangle] -pub static close: () = (); +pub static close: u8 = 127_u8; extern "C" { pub fn malloc(); diff --git a/tests/ui/lint/runtime-symbols-unix.rs b/tests/ui/lint/runtime-symbols-unix.rs index 517cf765195f3..4d6d0b60b9730 100644 --- a/tests/ui/lint/runtime-symbols-unix.rs +++ b/tests/ui/lint/runtime-symbols-unix.rs @@ -6,6 +6,7 @@ #![feature(c_variadic)] #![allow(clashing_extern_declarations)] // we are voluntarily testing different definitions +#![allow(improper_ctypes_definitions)] use core::ffi::{c_char, c_int, c_void}; diff --git a/tests/ui/lint/runtime-symbols-unix.stderr b/tests/ui/lint/runtime-symbols-unix.stderr index 7ef765c29684d..d287b4fc78448 100644 --- a/tests/ui/lint/runtime-symbols-unix.stderr +++ b/tests/ui/lint/runtime-symbols-unix.stderr @@ -1,5 +1,5 @@ error: invalid definition of the runtime `open` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:14:5 + --> $DIR/runtime-symbols-unix.rs:15:5 | LL | pub fn open() {} | ^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | pub fn open() {} = note: `#[deny(invalid_runtime_symbol_definitions)]` on by default error: invalid definition of the runtime `read` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:18:9 + --> $DIR/runtime-symbols-unix.rs:19:9 | LL | pub fn read(); | ^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | pub fn read(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "read")]`, or `#[link_name = "read"]` error: invalid definition of the runtime `write` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:21:9 + --> $DIR/runtime-symbols-unix.rs:22:9 | LL | pub fn write(); | ^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | pub fn write(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "write")]`, or `#[link_name = "write"]` error: invalid definition of the runtime `close` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:26:5 + --> $DIR/runtime-symbols-unix.rs:27:5 | LL | pub static close: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | pub static close: () = (); = help: either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = "close")]` error: invalid definition of the runtime `malloc` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:30:9 + --> $DIR/runtime-symbols-unix.rs:31:9 | LL | pub fn malloc(); | ^^^^^^^^^^^^^^^^ @@ -50,7 +50,7 @@ LL | pub fn malloc(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "malloc")]`, or `#[link_name = "malloc"]` error: invalid definition of the runtime `realloc` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:33:9 + --> $DIR/runtime-symbols-unix.rs:34:9 | LL | pub fn realloc(); | ^^^^^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL | pub fn realloc(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "realloc")]`, or `#[link_name = "realloc"]` error: invalid definition of the runtime `free` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:36:9 + --> $DIR/runtime-symbols-unix.rs:37:9 | LL | pub fn free(); | ^^^^^^^^^^^^^^ @@ -70,7 +70,7 @@ LL | pub fn free(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "free")]`, or `#[link_name = "free"]` error: invalid definition of the runtime `exit` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:39:9 + --> $DIR/runtime-symbols-unix.rs:40:9 | LL | pub fn exit(); | ^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ LL | pub fn exit(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "exit")]`, or `#[link_name = "exit"]` warning: suspicious definition of the runtime `open` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:46:9 + --> $DIR/runtime-symbols-unix.rs:47:9 | LL | pub fn open(path: *const U8, oflag: usize, ...) -> c_int; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,7 +92,7 @@ LL | pub fn open(path: *const U8, oflag: usize, ...) -> c_int; = note: `#[warn(suspicious_runtime_symbol_definitions)]` on by default warning: suspicious definition of the runtime `free` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:49:9 + --> $DIR/runtime-symbols-unix.rs:50:9 | LL | pub fn free(ptr: *const U8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -103,7 +103,7 @@ LL | pub fn free(ptr: *const U8); = help: allow this lint if the signature is compatible warning: suspicious definition of the runtime `exit` symbol used by the standard library - --> $DIR/runtime-symbols-unix.rs:52:9 + --> $DIR/runtime-symbols-unix.rs:53:9 | LL | pub fn exit(code: f32) -> !; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/runtime-symbols.rs b/tests/ui/lint/runtime-symbols.rs index 9b03f43c8cfaf..ee4df0cda15ba 100644 --- a/tests/ui/lint/runtime-symbols.rs +++ b/tests/ui/lint/runtime-symbols.rs @@ -5,6 +5,7 @@ #![feature(c_variadic)] #![allow(clashing_extern_declarations)] // we are voluntarily testing different definitions +#![allow(improper_ctypes_definitions)] use core::ffi::{c_char, c_int, c_void}; diff --git a/tests/ui/lint/runtime-symbols.stderr b/tests/ui/lint/runtime-symbols.stderr index 712f6532c1a59..0bd8635cf8acb 100644 --- a/tests/ui/lint/runtime-symbols.stderr +++ b/tests/ui/lint/runtime-symbols.stderr @@ -1,5 +1,5 @@ error: invalid definition of the runtime `memmove` symbol used by the standard library - --> $DIR/runtime-symbols.rs:13:5 + --> $DIR/runtime-symbols.rs:14:5 | LL | pub fn memmove() {} | ^^^^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | pub fn memmove() {} = note: `#[deny(invalid_runtime_symbol_definitions)]` on by default error: invalid definition of the runtime `memset` symbol used by the standard library - --> $DIR/runtime-symbols.rs:17:9 + --> $DIR/runtime-symbols.rs:18:9 | LL | pub fn memset(); | ^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | pub fn memset(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memset")]`, or `#[link_name = "memset"]` error: invalid definition of the runtime `memcmp` symbol used by the standard library - --> $DIR/runtime-symbols.rs:20:9 + --> $DIR/runtime-symbols.rs:21:9 | LL | pub fn memcmp(); | ^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | pub fn memcmp(); = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcmp")]`, or `#[link_name = "memcmp"]` error: invalid definition of the runtime `strlen` symbol used by the standard library - --> $DIR/runtime-symbols.rs:25:5 + --> $DIR/runtime-symbols.rs:26:5 | LL | pub static strlen: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | pub static strlen: () = (); = help: either fix the signature or remove any attributes `#[unsafe(no_mangle)]` or `#[unsafe(export_name = "strlen")]` error: invalid definition of the runtime `memcpy` symbol used by the standard library - --> $DIR/runtime-symbols.rs:30:5 + --> $DIR/runtime-symbols.rs:31:5 | LL | pub fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,7 +50,7 @@ LL | pub fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "memcpy")]`, or `#[link_name = "memcpy"]` error: invalid definition of the runtime `bcmp` symbol used by the standard library - --> $DIR/runtime-symbols.rs:37:5 + --> $DIR/runtime-symbols.rs:38:5 | LL | pub unsafe extern "C" fn bcmp(s1: *const c_void, s2: *const c_void, n: usize, _: ...) -> c_int { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL | pub unsafe extern "C" fn bcmp(s1: *const c_void, s2: *const c_void, n: = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "bcmp")]`, or `#[link_name = "bcmp"]` error: invalid definition of the runtime `bcmp` symbol used by the standard library - --> $DIR/runtime-symbols.rs:44:5 + --> $DIR/runtime-symbols.rs:45:5 | LL | pub extern "C" fn bcmp_(s1: *const c_void, s2: *const c_void, n: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -70,7 +70,7 @@ LL | pub extern "C" fn bcmp_(s1: *const c_void, s2: *const c_void, n: usize) = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "bcmp")]`, or `#[link_name = "bcmp"]` warning: suspicious definition of the runtime `memcpy` symbol used by the standard library - --> $DIR/runtime-symbols.rs:50:5 + --> $DIR/runtime-symbols.rs:51:5 | LL | pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,7 +82,7 @@ LL | pub extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, n: i64) = note: `#[warn(suspicious_runtime_symbol_definitions)]` on by default warning: suspicious definition of the runtime `memmove` symbol used by the standard library - --> $DIR/runtime-symbols.rs:56:5 + --> $DIR/runtime-symbols.rs:57:5 | LL | pub extern "C" fn memmove(dest: *mut c_void, src: *const c_void, n: i64) -> *mut c_void { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -93,7 +93,7 @@ LL | pub extern "C" fn memmove(dest: *mut c_void, src: *const c_void, n: i64 = help: allow this lint if the signature is compatible warning: suspicious definition of the runtime `memset` symbol used by the standard library - --> $DIR/runtime-symbols.rs:62:9 + --> $DIR/runtime-symbols.rs:63:9 | LL | fn memset(s: *mut c_void, c: c_int, n: usize) -> f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -104,7 +104,7 @@ LL | fn memset(s: *mut c_void, c: c_int, n: usize) -> f64; = help: allow this lint if the signature is compatible warning: suspicious definition of the runtime `bcmp` symbol used by the standard library - --> $DIR/runtime-symbols.rs:67:5 + --> $DIR/runtime-symbols.rs:68:5 | LL | pub extern "C" fn bcmp_(s1: *const U8, s2: *const U8, n: usize) -> c_int { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -115,7 +115,7 @@ LL | pub extern "C" fn bcmp_(s1: *const U8, s2: *const U8, n: usize) -> c_in = help: allow this lint if the signature is compatible warning: suspicious definition of the runtime `strlen` symbol used by the standard library - --> $DIR/runtime-symbols.rs:73:5 + --> $DIR/runtime-symbols.rs:74:5 | LL | pub extern "C" fn strlen(s: *const u64) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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..c215473de9f18 100644 --- a/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs +++ b/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs @@ -1,5 +1,7 @@ //@ build-pass +#![allow(improper_ctypes_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 48faef05cd44afe8ad6c706e913a9f1ada8955e9 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:21:27 +0200 Subject: [PATCH 14/17] ImproperCTypes: don't consider packed reprs `[repr(C,packed)]` structs shouldn't be considered FFI-safe --- .../rustc_lint/src/types/improper_ctypes.rs | 23 ++++++++++++------- .../repr-rust-is-undefined.stderr | 20 ++++------------ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index e730bca5c49ec..4da14b4e0891a 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -258,22 +258,21 @@ fn check_struct_for_power_alignment<'tcx>( item: &'tcx hir::Item<'tcx>, adt_def: AdtDef<'tcx>, ) { - let tcx = cx.tcx; - // Only consider structs (not enums or unions) on AIX. - if tcx.sess.target.os != Os::Aix || !adt_def.is_struct() { + if cx.tcx.sess.target.os != Os::Aix || !adt_def.is_struct() { return; } // The struct must be repr(C), but ignore it if it explicitly specifies its alignment with // either `align(N)` or `packed(N)`. - if adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none() { + debug_assert!(adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none()); + if cx.tcx.sess.target.os == Os::Aix && !adt_def.all_fields().next().is_none() { let struct_variant_data = item.expect_struct().2; for field_def in struct_variant_data.fields().iter().skip(1) { // Struct fields (after the first field) are checked for the // power alignment rule, as fields after the first are likely // to be the fields that are misaligned. - let ty = tcx.type_of(field_def.def_id).instantiate_identity().skip_norm_wip(); + let ty = cx.tcx.type_of(field_def.def_id).instantiate_identity().skip_norm_wip(); if check_arg_for_power_alignment(cx, ty) { cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment); } @@ -1130,7 +1129,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // otherwise, having all fields be phantoms // takes priority over transparent_with_all_zst_fields if let FfiUnsafe(explanations) = ffires_accumulator { - debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some()); + debug_assert!( + (def.repr().c() && !def.repr().packed()) + || def.repr().transparent() + || def.repr().int.is_some() + ); if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) { let field_ffires = FfiUnsafe(explanations).wrap_all( @@ -1197,7 +1200,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, msg!("`{$ty}` has unspecified layout"), @@ -1256,7 +1260,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.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 b763570ef1549..2219f42dc7018 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 this struct + = 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 this struct - = 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 this struct + = 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 this struct - = 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 8e9fa4e46450067350101682d962f1f0a43e140b Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:22:38 +0200 Subject: [PATCH 15/17] ImproperCTypes: add tests - ensure proper coverage of as many edge cases in the type checking as possible - test for an issue that was fixed by this commit chain - understand where `[allow(improper_c*)]` needs to be to take effect (current behaviour not ideal, one should be able to flag a struct definitition as safe anyway) --- .../improper-ctypes/allow-improper-ctypes.rs | 159 +++++++++ .../allow-improper-ctypes.stderr | 104 ++++++ .../auxiliary/extern_crate_types.rs | 85 +++++ .../improper-ctypes/lint-nonexhaustive.rs | 35 ++ .../lint/improper-ctypes/lint-tykind-fuzz.rs | 311 ++++++++++++++++++ .../improper-ctypes/lint-tykind-fuzz.stderr | 292 ++++++++++++++++ ...int_uninhabited.rs => lint-uninhabited.rs} | 0 ...habited.stderr => lint-uninhabited.stderr} | 36 +- 8 files changed, 1004 insertions(+), 18 deletions(-) create mode 100644 tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs create mode 100644 tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr create mode 100644 tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr rename tests/ui/lint/improper-ctypes/{lint_uninhabited.rs => lint-uninhabited.rs} (100%) rename tests/ui/lint/improper-ctypes/{lint_uninhabited.stderr => lint-uninhabited.stderr} (85%) diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs new file mode 100644 index 0000000000000..a0cf32237179c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs @@ -0,0 +1,159 @@ +#![deny(improper_ctypes, improper_ctypes_definitions)] + +//@ aux-build: extern_crate_types.rs +//@ compile-flags:--extern extern_crate_types +extern crate extern_crate_types as ext_crate; + +// //////////////////////////////////////////////////////// +// first, the same bank of types as in the extern crate + +// FIXME: maybe re-introduce improper_ctype_definitions (ctype singular) +// as a way to mark ADTs as "let's ignore that they are not actually FFI-unsafe" + +#[repr(C)] +struct SafeStruct (i32); + +#[repr(C)] +struct UnsafeStruct (String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeStruct (String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, +// aliasing, lifetimes, etc...) +// the lint is not raised here, but will be if used in the wrong place +#[repr(C)] +struct UnsafeFromForeignStruct<'a> (&'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeFromForeignStruct<'a> (&'a u32); + + +type SafeFnPtr = extern "C" fn(i32)->i32; + +type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; +//~^ ERROR: `extern` callback uses type `(i32, i32)` + + +// for now, let's not lint on the nonzero assumption, +// because: +// - we don't know if the callback is rust-callee-foreign-caller or the other way around +// - having to cast around function signatures to get function pointers +// would be an awful experience +// so, let's assume that the unsafety in this fnptr +// will be pointed out indirectly by a lint elsewhere +// (note: there's one case where the error would be missed altogether: +// a rust-caller,non-rust-callee callback where the fnptr +// is given as an argument to a rust-callee,non-rust-caller +// FFI boundary) +#[allow(improper_ctypes)] +type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_ctypes)] +type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_ctypes)] +type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////////////////////////// +// then, some functions that use them + +static INT: u32 = 42; + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn1a(e: &String) -> &str {&*e} +extern "C" fn fn1u(e: &String) -> &str {&*e} +//~^ ERROR: `extern` fn uses type `&str` +// | FIXME: not warning about the &String feels wrong, but it's behind a FFI-safe reference so... + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2a(e: UnsafeStruct) {} +extern "C" fn fn2u(e: UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `UnsafeStruct` +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2oa(e: ext_crate::UnsafeStruct) {} +extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::UnsafeStruct` + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3a(e: AllowedUnsafeStruct) {} +extern "C" fn fn3u(e: AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3oa(e: ext_crate::AllowedUnsafeStruct) {} +extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4a(e: UnsafeFromForeignStruct) {} +extern "C" fn fn4u(e: UnsafeFromForeignStruct) {} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4oa(e: ext_crate::UnsafeFromForeignStruct) {} +extern "C" fn fn4ou(e: ext_crate::UnsafeFromForeignStruct) {} +// the block above might become unsafe if/once we lint on the value assumptions of types + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5a() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +extern "C" fn fn5u() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5oa() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} +extern "C" fn fn5ou() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6a() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6u() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6oa() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6ou() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} + +// //////////////////////////////////////////////////////// +// special cases: struct-in-fnptr and fnptr-in-struct + +#[repr(C)] +struct FakeVTable{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + //~^ ERROR: `extern` callback uses type `&[A]` + drop: extern "C" fn(A), + something_else: (A, usize), +} + +type FakeVTableMaker = extern "C" fn() -> FakeVTable; +//~^ ERROR: `extern` callback uses type `FakeVTable` + +#[repr(C)] +#[allow(improper_ctypes)] +struct FakeVTableAllowed{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + drop: extern "C" fn(A), + something_else: (A, usize), +} + +#[allow(improper_ctypes)] +type FakeVTableMakerAllowed = extern "C" fn() -> FakeVTable; + +fn main(){} diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr new file mode 100644 index 0000000000000..8da13d1e58f15 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr @@ -0,0 +1,104 @@ +error: `extern` callback uses type `(i32, i32)`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:38:34 + | +LL | type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:74:35 + | +LL | extern "C" fn fn1u(e: &String) -> &str {&*e} + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:80:23 + | +LL | extern "C" fn fn2u(e: UnsafeStruct) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`UnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:17:1 + | +LL | struct UnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:84:24 + | +LL | extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::UnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `String` has unspecified layout + +error: `extern` fn uses type `AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:89:23 + | +LL | extern "C" fn fn3u(e: AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:21:1 + | +LL | struct AllowedUnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:94:24 + | +LL | extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `String` has unspecified layout + +error: `extern` callback uses type `&[A]`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:138:28 + | +LL | combine: extern "C" fn(&[A]) -> A, + | ^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` callback uses type `FakeVTable`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:144:43 + | +LL | type FakeVTableMaker = extern "C" fn() -> FakeVTable; + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`FakeVTable`) is FFI-unsafe due to a `(u32, usize)` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:136:1 + | +LL | struct FakeVTable{ + | ^^^^^^^^^^^^^^^^^^^^ + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: aborting due to 8 previous errors + diff --git a/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs new file mode 100644 index 0000000000000..3337bd0f588a8 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs @@ -0,0 +1,85 @@ +/// a bank of types (structs, function pointers) that are safe or unsafe for whatever reason, +/// with or without said unsafety being explicitely ignored + +#[repr(C)] +pub struct SafeStruct (pub i32); + +#[repr(C)] +pub struct UnsafeStruct (pub String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeStruct (pub String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, aliasing, +// lifetimes, etc...) +#[repr(C)] +pub struct UnsafeFromForeignStruct<'a> (pub &'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeFromForeignStruct<'a> (pub &'a u32); + + +pub type SafeFnPtr = extern "C" fn(i32)->i32; + +pub type UnsafeFnPtr = extern "C" fn((i32,i32))->i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +pub type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +pub type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////// +/// types used in specific issue-based tests that need extern-crate types + +#[repr(C)] +#[non_exhaustive] +pub struct NonExhaustiveStruct { + pub field: u8 +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveEnum { + variant(u8), +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveCEnum { + variant1, + variant2(()), +} + +#[repr(C)] +pub enum NonExhaustiveEnumVariant { + variant1, + #[non_exhaustive] + variant2((u32,)), +} + +extern "C" { + pub fn nonexhaustivestruct_create() -> *mut NonExhaustiveStruct; + pub fn nonexhaustivestruct_destroy(s: *mut NonExhaustiveStruct); + pub fn nonexhaustiveenum_create() -> *mut NonExhaustiveEnum; + pub fn nonexhaustiveenum_destroy(s: *mut NonExhaustiveEnum); + pub fn nonexhaustivecenum_create() -> *mut NonExhaustiveCEnum; + pub fn nonexhaustivecenum_destroy(s: *mut NonExhaustiveCEnum); + pub fn nonexhaustiveenumvariant_create() -> *mut NonExhaustiveEnumVariant; + pub fn nonexhaustiveenumvariant_destroy(s: *mut NonExhaustiveEnumVariant); + + pub fn nonexhaustivestruct_onstack() -> NonExhaustiveStruct; + pub fn nonexhaustivestruct_owned() -> NonExhaustiveStruct; +} diff --git a/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs b/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs new file mode 100644 index 0000000000000..bcb5fbd6d593b --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs @@ -0,0 +1,35 @@ +//@ check-pass +#![deny(improper_ctypes)] + +//@ aux-build: extern_crate_types.rs +//@ compile-flags:--extern extern_crate_types +extern crate extern_crate_types as ext_crate; + +// Properly deal with non_exhaustive types: +// the thorny logic is expressed in https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 +// and its linked comments +// + +// Issue: https://github.com/rust-lang/rust/issues/132699 +// FFI-safe pointers to nonexhaustive structs should be FFI-safe too + +// BEGIN: this is the exact same code as in ext_crate, to compare the lints +#[repr(C)] +#[non_exhaustive] +pub struct OtherNonExhaustiveStruct { + pub field: u8 +} + +extern "C" { + pub fn othernonexhaustivestruct_create() -> *mut OtherNonExhaustiveStruct; + pub fn othernonexhaustivestruct_destroy(s: *mut OtherNonExhaustiveStruct); +} +// END + +use ext_crate::NonExhaustiveStruct; + +extern "C" { + pub fn use_struct(s: *mut NonExhaustiveStruct); +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs new file mode 100644 index 0000000000000..d1b821d31e47d --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs @@ -0,0 +1,311 @@ +// Trying to cover as many ty_kinds as possible in the code for ImproperCTypes lint +//@ edition:2018 + +#![allow(dead_code,unused_variables)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +// we want ALL the ty_kinds, including the feature-gated ones +#![feature(extern_types)] +#![feature(never_type)] +#![feature(inherent_associated_types)] +#![feature(async_trait_bounds)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] + +// ty_kinds not found so far: +// Placeholder, Bound, Infer, Error, +// Alias +// FnDef, Closure, Coroutine, ClosureCoroutine, CoroutineWitness, + +use std::ptr::from_ref; +use std::ptr::NonNull; +use std::mem::{MaybeUninit, size_of}; +use std::num::NonZero; +use std::pat::pattern_type; + +#[repr(C)] +struct SomeStruct{ + a: u8, + b: i32, +} +impl SomeStruct{ + extern "C" fn klol( + // Ref[Struct] + &self + ){} +} + +#[repr(C)] +#[derive(Clone,Copy)] +struct TemplateStruct where T: std::ops::Add+Copy { + one: T, + two: T, +} +impl TemplateStruct { + type Out = ::Output; +} + +extern "C" fn tstruct_sum( + // Ref[Struct] + slf: Option<&TemplateStruct> + // Option> ...not Inherent. dangit +) -> Option::Out>> { + Some(Box::new(slf?.one + slf?.two)) +} + +#[repr(C)] +union SomeUnion{ + sz: u8, + us: i8, +} +#[repr(C)] +enum SomeEnum{ + Everything=42, + NotAU8=256, + SomePrimeNumber=23, +} + +pub trait TimesTwo: std::ops::Add + Sized + Clone + where for<'a> &'a Self: std::ops::Add<&'a Self>, + *const Self: std::ops::Add<*const Self>, + Box: std::ops::Add>, +{ + extern "C" fn t2_own( + // Param + self + // Alias + ) -> >::Output { + self.clone() + self + } + // it ICEs (https://github.com/rust-lang/rust/issues/134587) :( + //extern "C" fn t2_ptr( + // // Ref[Param] + // slf: *const Self + // // Alias + //) -> <*const Self as std::ops::Add<*const Self>>::Output { + // slf + slf + //} + extern "C" fn t2_box( + // Box[Param] + self: Box, + // Alias + ) -> as std::ops::Add>>::Output { + self.clone() + self + } + extern "C" fn t2_ref( + // Ref[Param] + &self + // Alias + ) -> <&Self as std::ops::Add<&Self>>::Output { + self + self + } +} + +extern "C" {type ExtType;} + +#[repr(C)] +pub struct StructWithDyn(dyn std::fmt::Debug); + +extern "C" { + // variadic args aren't listed as args in a way that allows type checking. + // this is fine (TM) + fn variadic_function(e: ...); +} + +extern "C" fn all_ty_kinds<'a,const N:usize,T>( + // UInt, Int, Float, Bool + u:u8, i:i8, f:f64, b:bool, + // Struct + s:String, //~ ERROR: uses type `String` + // Ref[Str] + s2:&str, //~ ERROR: uses type `&str` + // Char + c: char, //~ ERROR: uses type `char` + // Ref[Slice] + s3:&[u8], //~ ERROR: uses type `&[u8]` + // Array (this gets caught outside of the code we want to test) + s4:[u8;N], //~ ERROR: uses type `[u8; N]` + // Tuple + p:(u8, u8), //~ ERROR: uses type `(u8, u8)` + // also Tuple + (p2, p3):(u8, u8), //~ ERROR: uses type `(u8, u8)` + // Pat + nz: pattern_type!(u32 is 1..), + // Struct + SomeStruct{b:p4,..}: SomeStruct, + // Union + u2: SomeUnion, + // Enum, + e: SomeEnum, + // Param + d: impl Clone, + // Param + t: T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Never + x:!, //~ ERROR: uses type `!` + //r1: &u8, r2: *const u8, r3: Box, + // FnPtr + f2: fn(u8)->u8, //~ ERROR: uses type `fn(u8) -> u8` + // Ref[Dynamic] + f3: &'a dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // Param, + a: impl async Fn(u8)->u8, //FIXME: eventually, be able to peer into type params + // Alias +) -> impl std::fmt::Debug { //~ ERROR: uses type `impl Debug` + 3_usize +} + +extern "C" fn all_ty_kinds_in_ptr( + // Ptr[UInt], Ptr[Int], Ptr[Float], Ptr[Bool] + u: *const u8, i: *const i8, f: *const f64, b: *const bool, + // Ptr[Struct] + s: *const String, + // Ptr[Str] + s2: *const str, //~ ERROR: uses type `*const str` + // Ptr[Char] + c: *const char, + // Ptr[Slice] + s3: *const [u8], //~ ERROR: uses type `*const [u8]` + // Ptr[Array] (this gets caught outside of the code we want to test) + s4: *const [u8;N], + // Ptr[Tuple] + p: *const (u8,u8), + // Tuple + (p2, p3):(*const u8, *const u8), //~ ERROR: uses type `(*const u8, *const u8)` + // Pat + nz: *const pattern_type!(u32 is 1..), + // Ptr[Struct] + SomeStruct{b: ref p4,..}: & SomeStruct, + // Ptr[Union] + u2: *const SomeUnion, + // Ptr[Enum], + e: *const SomeEnum, + // Param + d: *const impl Clone, + // Param + t: *const T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ptr[Struct] + e3: *const StructWithDyn, //~ ERROR: uses type `*const StructWithDyn` + // Ptr[Never] + x: *const !, + //r1: &u8, r2: *const u8, r3: Box, + // Ptr[FnPtr] + f2: *const fn(u8)->u8, + // Ptr[Dynamic] + f3: *const dyn Fn(u8)->u8, //~ ERROR: uses type `*const dyn Fn(u8) -> u8` + // Ptr[Dynamic] + d2: *const dyn std::cmp::PartialOrd, //~ ERROR: uses type `*const dyn PartialOrd` + // Ptr[Param], + a: *const impl async Fn(u8)->u8, + // Alias +) -> *const dyn std::fmt::Debug { //~ ERROR: uses type `*const dyn Debug` + todo!() +} + +extern "C" { +fn all_ty_kinds_in_ref<'a>( + // Ref[UInt], Ref[Int], Ref[Float], Ref[Bool] + u: &u8, i: &'a i8, f: &f64, b: &bool, + // Ref[Struct] + s: &String, + // Ref[Str] + s2: &str, //~ ERROR: uses type `&str` + // Ref[Char] + c: &char, + // Ref[Slice] + s3: &[u8], //~ ERROR: uses type `&[u8]` + // deactivated here, because this is a function *declaration* (param N unacceptable) + // s4: &[u8;N], + // Ref[Tuple] + p: &(u8, u8), + // deactivated here, because this is a function *declaration* (patterns unacceptable) + // (p2, p3):(&u8, &u8), + // Pat + nz: &pattern_type!(u32 is 1..), + // deactivated here, because this is a function *declaration* (pattern unacceptable) + // SomeStruct{b: ref p4,..}: &SomeStruct, + // Ref[Union] + u2: &SomeUnion, + // Ref[Enum], + e: &SomeEnum, + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // d: &impl Clone, + // deactivated here, because this is a function *declaration* (type param unacceptable) + // t: &T, + // Ref[Foreign] + e2: &ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Ref[Never] + x: &!, + //r1: &u8, r2: &u8, r3: Box, + // Ref[FnPtr] + f2: &fn(u8)->u8, + // Ref[Dynamic] + f3: &dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // a: &impl async Fn(u8)->u8, + // Ref[Dynamic] +) -> &'a dyn std::fmt::Debug; //~ ERROR: uses type `&dyn Debug` +} + +extern "C" fn all_ty_kinds_in_box( + // Box[UInt], Box[Int], Box[Float], Box[Bool] + u: Option>, i: Option>, f: Option>, b: Option>, + // Box[Struct] + s: Option>, + // Box[Str] + s2: Box, //~ ERROR: uses type `Box` + // Box[Char] + c: Box, + // Box[Slice] + s3: Box<[u8]>, //~ ERROR: uses type `Box<[u8]>` + // Box[Array] (this gets caught outside of the code we want to test) + s4: Option>, + // Box[Tuple] + p: Option>, + // also Tuple + (p2,p3):(Box, Box), //~ ERROR: uses type `(Box, Box)` + // Pat + nz: Option>, + // Ref[Struct] + SomeStruct{b: ref p4,..}: &SomeStruct, + // Box[Union] + u2: Option>, + // Box[Enum], + e: Option>, + // Box[Param] + d: Option>, + // Box[Param] + t: Option>, + // deactivated, we can't deallocate an external type in rust + //e2: Option>, + // Box[Struct] + e3: Box, //~ ERROR: uses type `Box` + // Box[Never] + x: Box, + //r1: Box, + // Box[FnPtr] + f2: Boxu8>, + // Box[Dynamic] + f3: Boxu8>, //~ ERROR: uses type `Box u8>` + // Box[Dynamic] + d2: Box>, //~ ERROR: uses type `Box>` + // Option[Box[Param]], + a: Optionu8>>, + // Box[Dynamic] +) -> Box { //~ ERROR: uses type `Box` + u.unwrap() +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr new file mode 100644 index 0000000000000..3726757d58e2f --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr @@ -0,0 +1,292 @@ +error: `extern` fn uses type `String`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:119:7 + | +LL | s:String, + | ^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct + = note: `String` has unspecified layout +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:121:8 + | +LL | s2:&str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `char`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:123:8 + | +LL | c: char, + | ^^^^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` fn uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:125:8 + | +LL | s3:&[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `[u8; N]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:127:8 + | +LL | s4:[u8;N], + | ^^^^^^ not FFI-safe + | + = help: consider passing a pointer to the array + = note: passing raw arrays by value is not FFI-safe + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:129:7 + | +LL | p:(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:131:14 + | +LL | (p2, p3):(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:147:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `!`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:149:7 + | +LL | x:!, + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:152:9 + | +LL | f2: fn(u8)->u8, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using an `extern fn(...) -> ...` function pointer instead + = note: this function pointer has a Rust-specific calling convention + +error: `extern` fn uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:154:9 + | +LL | f3: &'a dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:156:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `impl Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:160:6 + | +LL | ) -> impl std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: opaque types have no C equivalent + +error: `extern` fn uses type `*const str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:170:9 + | +LL | s2: *const str, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const [u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:174:9 + | +LL | s3: *const [u8], + | ^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(*const u8, *const u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:180:14 + | +LL | (p2, p3):(*const u8, *const u8), + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `*const StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:196:9 + | +LL | e3: *const StructWithDyn, + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:203:9 + | +LL | f3: *const dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:205:9 + | +LL | d2: *const dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:209:6 + | +LL | ) -> *const dyn std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:220:9 + | +LL | s2: &str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:224:9 + | +LL | s3: &[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:246:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:253:9 + | +LL | f3: &dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:255:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:259:6 + | +LL | ) -> &'a dyn std::fmt::Debug; + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:268:9 + | +LL | s2: Box, + | ^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:272:9 + | +LL | s3: Box<[u8]>, + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(Box, Box)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:278:13 + | +LL | (p2,p3):(Box, Box), + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:294:9 + | +LL | e3: Box, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box u8>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:301:9 + | +LL | f3: Boxu8>, + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:303:9 + | +LL | d2: Box>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:307:6 + | +LL | ) -> Box { + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: aborting due to 33 previous errors + diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.rs b/tests/ui/lint/improper-ctypes/lint-uninhabited.rs similarity index 100% rename from tests/ui/lint/improper-ctypes/lint_uninhabited.rs rename to tests/ui/lint/improper-ctypes/lint-uninhabited.rs diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr b/tests/ui/lint/improper-ctypes/lint-uninhabited.stderr similarity index 85% rename from tests/ui/lint/improper-ctypes/lint_uninhabited.stderr rename to tests/ui/lint/improper-ctypes/lint-uninhabited.stderr index 9488dd0f62378..852b8115a5d46 100644 --- a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr +++ b/tests/ui/lint/improper-ctypes/lint-uninhabited.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `AlsoUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:33:17 + --> $DIR/lint-uninhabited.rs:33:17 | LL | fn bad_entry(e: AlsoUninhabited); | ^^^^^^^^^^^^^^^ not FFI-safe @@ -7,37 +7,37 @@ LL | fn bad_entry(e: AlsoUninhabited); = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:11:1 + --> $DIR/lint-uninhabited.rs:11:1 | LL | struct AlsoUninhabited{ | ^^^^^^^^^^^^^^^^^^^^^^ = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint_uninhabited.rs:4:9 + --> $DIR/lint-uninhabited.rs:4:9 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:36:18 + --> $DIR/lint-uninhabited.rs:36:18 | LL | fn bad0_entry(e: Uninhabited); | ^^^^^^^^^^^ not FFI-safe | = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ error: `extern` block uses type `!`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:42:18 + --> $DIR/lint-uninhabited.rs:42:18 | LL | fn never_entry(e:!); | ^ not FFI-safe @@ -45,7 +45,7 @@ LL | fn never_entry(e:!); = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables error: `extern` fn uses type `AlsoUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:47:33 + --> $DIR/lint-uninhabited.rs:47:33 | LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} | ^^^^^^^^^^^^^^^ not FFI-safe @@ -53,50 +53,50 @@ LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:11:1 + --> $DIR/lint-uninhabited.rs:11:1 | LL | struct AlsoUninhabited{ | ^^^^^^^^^^^^^^^^^^^^^^ = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint_uninhabited.rs:4:26 + --> $DIR/lint-uninhabited.rs:4:26 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:55:34 + --> $DIR/lint-uninhabited.rs:55:34 | LL | extern "C" fn impl_bad0_entry(e: Uninhabited) {} | ^^^^^^^^^^^ not FFI-safe | = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ warning: the type `Uninhabited` does not permit zero-initialization - --> $DIR/lint_uninhabited.rs:57:12 + --> $DIR/lint-uninhabited.rs:57:12 | LL | unsafe{transmute(())} | ^^^^^^^^^^^^^ this code causes undefined behavior when executed | note: enums with no inhabited variants have no valid value - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ = note: `#[warn(invalid_value)]` on by default error: `extern` fn uses type `!`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:65:34 + --> $DIR/lint-uninhabited.rs:65:34 | LL | extern "C" fn impl_never_entry(e:!){} | ^ not FFI-safe @@ -104,14 +104,14 @@ LL | extern "C" fn impl_never_entry(e:!){} = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables error: `extern` fn uses type `HalfHiddenUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:70:31 + --> $DIR/lint-uninhabited.rs:70:31 | LL | extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`HalfHiddenUninhabited`) is FFI-unsafe due to a `!` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:25:1 + --> $DIR/lint-uninhabited.rs:25:1 | LL | struct HalfHiddenUninhabited { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From b50a14c91659c789049ecd71d3382e356ec55882 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:36:17 +0200 Subject: [PATCH 16/17] 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 d63d48e56903d..c28131f705ebd 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -34,7 +34,7 @@ asm_experimental_arch, unboxed_closures )] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] #![no_std] #![no_core] From 12f0424eaa673eef49ee3fe607601c1025847fdd Mon Sep 17 00:00:00 2001 From: niacdoial Date: Fri, 29 Aug 2025 21:13:12 +0200 Subject: [PATCH 17/17] ImproperCTypes: rename associated tests Rustc is trying to shift away from test names that are just issue numbers, so we add this as part of its effort, since this commit chain is moving and rewriting these files anyway. The directory containing all tests is also renamed. --- .../{lint-94223.rs => ice-fnptr-slicearg.rs} | 5 ++- ...94223.stderr => ice-fnptr-slicearg.stderr} | 38 +++++++++---------- ...ss-73249-1.rs => ice-fully-normalize-1.rs} | 3 ++ ...nt-73249-2.rs => ice-fully-normalize-2.rs} | 3 ++ ...nt-73249-3.rs => ice-fully-normalize-3.rs} | 3 ++ ...-5.stderr => ice-fully-normalize-3.stderr} | 6 +-- ...ss-73249-4.rs => ice-fully-normalize-4.rs} | 3 ++ ...nt-73249-5.rs => ice-fully-normalize-5.rs} | 3 ++ ...-3.stderr => ice-fully-normalize-5.stderr} | 6 +-- ...stpass-73249.rs => ice-fully-normalize.rs} | 3 ++ ...ustpass-73747.rs => ice-normalize-cast.rs} | 3 ++ .../{mustpass-113900.rs => ice-normalize.rs} | 1 + ...tpass-134060.rs => ice-tykind-coverage.rs} | 2 + ...4060.stderr => ice-tykind-coverage.stderr} | 2 +- ...ss-73251.rs => issue-associated-opaque.rs} | 4 ++ ...436-1.rs => issue-fnptr-wrapped-unit-1.rs} | 3 ++ ...derr => issue-fnptr-wrapped-unit-1.stderr} | 14 +++---- ...-113436.rs => issue-fnptr-wrapped-unit.rs} | 5 ++- ...ass-66202.rs => issue-normalize-return.rs} | 1 + ...t-73251-1.rs => issue-project-opaque-1.rs} | 4 ++ ...1.stderr => issue-project-opaque-1.stderr} | 4 +- ...t-73251-2.rs => issue-project-opaque-2.rs} | 4 ++ ...2.stderr => issue-project-opaque-2.stderr} | 4 +- .../lint/improper-ctypes/lint-ctypes.stderr | 2 +- 24 files changed, 86 insertions(+), 40 deletions(-) rename tests/ui/lint/improper-ctypes/{lint-94223.rs => ice-fnptr-slicearg.rs} (90%) rename tests/ui/lint/improper-ctypes/{lint-94223.stderr => ice-fnptr-slicearg.stderr} (86%) rename tests/ui/lint/improper-ctypes/{mustpass-73249-1.rs => ice-fully-normalize-1.rs} (74%) rename tests/ui/lint/improper-ctypes/{lint-73249-2.rs => ice-fully-normalize-2.rs} (84%) rename tests/ui/lint/improper-ctypes/{lint-73249-3.rs => ice-fully-normalize-3.rs} (76%) rename tests/ui/lint/improper-ctypes/{lint-73249-5.stderr => ice-fully-normalize-3.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-73249-4.rs => ice-fully-normalize-4.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-73249-5.rs => ice-fully-normalize-5.rs} (76%) rename tests/ui/lint/improper-ctypes/{lint-73249-3.stderr => ice-fully-normalize-5.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-73249.rs => ice-fully-normalize.rs} (73%) rename tests/ui/lint/improper-ctypes/{mustpass-73747.rs => ice-normalize-cast.rs} (67%) rename tests/ui/lint/improper-ctypes/{mustpass-113900.rs => ice-normalize.rs} (83%) rename tests/ui/lint/improper-ctypes/{mustpass-134060.rs => ice-tykind-coverage.rs} (90%) rename tests/ui/lint/improper-ctypes/{mustpass-134060.stderr => ice-tykind-coverage.stderr} (90%) rename tests/ui/lint/improper-ctypes/{mustpass-73251.rs => issue-associated-opaque.rs} (63%) rename tests/ui/lint/improper-ctypes/{lint-113436-1.rs => issue-fnptr-wrapped-unit-1.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-113436-1.stderr => issue-fnptr-wrapped-unit-1.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-113436.rs => issue-fnptr-wrapped-unit.rs} (78%) rename tests/ui/lint/improper-ctypes/{mustpass-66202.rs => issue-normalize-return.rs} (87%) rename tests/ui/lint/improper-ctypes/{lint-73251-1.rs => issue-project-opaque-1.rs} (66%) rename tests/ui/lint/improper-ctypes/{lint-73251-1.stderr => issue-project-opaque-1.stderr} (81%) rename tests/ui/lint/improper-ctypes/{lint-73251-2.rs => issue-project-opaque-2.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-73251-2.stderr => issue-project-opaque-2.stderr} (81%) diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs similarity index 90% rename from tests/ui/lint/improper-ctypes/lint-94223.rs rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs index 0c8d531f69247..8276329d5dd84 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs @@ -1,5 +1,8 @@ #![crate_type = "lib"] -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +// Issue: https://github.com/rust-lang/rust/issues/94223 +// ice when a FnPtr has an unsized array argument pub fn bad(f: extern "C" fn([u8])) {} //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr similarity index 86% rename from tests/ui/lint/improper-ctypes/lint-94223.stderr rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr index a1e81a2929e7a..c7487cf4af932 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr @@ -1,5 +1,5 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:4:29 + --> $DIR/ice-fnptr-slicearg.rs:7:29 | LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^ not FFI-safe @@ -7,13 +7,13 @@ LL | pub fn bad(f: extern "C" fn([u8])) {} = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-94223.rs:2:38 + --> $DIR/ice-fnptr-slicearg.rs:2:9 | -LL | #![deny(improper_ctypes_definitions, improper_ctypes)] - | ^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:42 + --> $DIR/ice-fnptr-slicearg.rs:10:42 | LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe @@ -22,7 +22,7 @@ LL | pub fn bad_twice(f: Result) {} = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:63 + --> $DIR/ice-fnptr-slicearg.rs:10:63 | LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe @@ -31,7 +31,7 @@ LL | pub fn bad_twice(f: Result) {} = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:11:32 + --> $DIR/ice-fnptr-slicearg.rs:14:32 | LL | struct BadStruct(extern "C" fn([u8])); | ^^^^ not FFI-safe @@ -40,7 +40,7 @@ LL | struct BadStruct(extern "C" fn([u8])); = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:15:21 + --> $DIR/ice-fnptr-slicearg.rs:18:21 | LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe @@ -49,7 +49,7 @@ LL | A(extern "C" fn([u8])), = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:20:21 + --> $DIR/ice-fnptr-slicearg.rs:23:21 | LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe @@ -58,7 +58,7 @@ LL | A(extern "C" fn([u8])), = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:24:26 + --> $DIR/ice-fnptr-slicearg.rs:27:26 | LL | type Foo = extern "C" fn([u8]); | ^^^^ not FFI-safe @@ -67,7 +67,7 @@ LL | type Foo = extern "C" fn([u8]); = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe - --> $DIR/lint-94223.rs:31:34 + --> $DIR/ice-fnptr-slicearg.rs:34:34 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -76,7 +76,7 @@ LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); = note: enum has no representation hint error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:41:31 + --> $DIR/ice-fnptr-slicearg.rs:44:31 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe @@ -84,13 +84,13 @@ LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct = note: `FfiUnsafe` has unspecified layout note: the type is defined here - --> $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:44 + --> $DIR/ice-fnptr-slicearg.rs:47:44 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe @@ -98,13 +98,13 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:70 + --> $DIR/ice-fnptr-slicearg.rs:47:70 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe @@ -112,13 +112,13 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:48:36 + --> $DIR/ice-fnptr-slicearg.rs:51:36 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe @@ -126,7 +126,7 @@ LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct = note: `FfiUnsafe` has unspecified layout note: the type is defined here - --> $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249-1.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs similarity index 74% rename from tests/ui/lint/improper-ctypes/mustpass-73249-1.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs index 0ca91ef294f05..6fef795351e15 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249-1.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Foo { type Assoc: 'static; } diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs similarity index 84% rename from tests/ui/lint/improper-ctypes/lint-73249-2.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs index 9286d822e22e3..006c8b27306b6 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs @@ -3,6 +3,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + trait Baz {} impl Baz for () {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs similarity index 76% rename from tests/ui/lint/improper-ctypes/lint-73249-3.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs index aff2a182e3f49..31a69bd78f081 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs @@ -1,6 +1,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-73249-5.stderr rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr index f42924f4d5b56..b19975ec9dad1 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr @@ -1,18 +1,18 @@ error: `extern` block uses type `A`, which is not FFI-safe - --> $DIR/lint-73249-5.rs:21:25 + --> $DIR/ice-fully-normalize-3.rs:24:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field note: the type is defined here - --> $DIR/lint-73249-5.rs:16:1 + --> $DIR/ice-fully-normalize-3.rs:19:1 | LL | pub struct A { | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73249-5.rs:2:9 + --> $DIR/ice-fully-normalize-3.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249-4.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/mustpass-73249-4.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs index 37099c1313ade..785d002cae8b5 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249-4.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + use std::marker::PhantomData; trait Foo { diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs similarity index 76% rename from tests/ui/lint/improper-ctypes/lint-73249-5.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs index 8ad5be4e6301e..61d2a06101538 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs @@ -1,6 +1,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-73249-3.stderr rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr index dc6f6fb08ed33..c8513322fbbc8 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr @@ -1,18 +1,18 @@ error: `extern` block uses type `A`, which is not FFI-safe - --> $DIR/lint-73249-3.rs:21:25 + --> $DIR/ice-fully-normalize-5.rs:24:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field note: the type is defined here - --> $DIR/lint-73249-3.rs:16:1 + --> $DIR/ice-fully-normalize-5.rs:19:1 | LL | pub struct A { | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73249-3.rs:2:9 + --> $DIR/ice-fully-normalize-5.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize.rs similarity index 73% rename from tests/ui/lint/improper-ctypes/mustpass-73249.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize.rs index c5f2318ef0af0..a0f8d1ce18665 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Foo { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/mustpass-73747.rs b/tests/ui/lint/improper-ctypes/ice-normalize-cast.rs similarity index 67% rename from tests/ui/lint/improper-ctypes/mustpass-73747.rs rename to tests/ui/lint/improper-ctypes/ice-normalize-cast.rs index a2562e3b4213b..e779b599f7a4a 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73747.rs +++ b/tests/ui/lint/improper-ctypes/ice-normalize-cast.rs @@ -1,5 +1,8 @@ //@ check-pass +// Issue: https://github.com/rust-lang/rust/issues/73747 +// ICE that seems to happen in type normalization when dealing with casts + #[repr(transparent)] struct NonNullRawComPtr { inner: std::ptr::NonNull<::VTable>, diff --git a/tests/ui/lint/improper-ctypes/mustpass-113900.rs b/tests/ui/lint/improper-ctypes/ice-normalize.rs similarity index 83% rename from tests/ui/lint/improper-ctypes/mustpass-113900.rs rename to tests/ui/lint/improper-ctypes/ice-normalize.rs index 3dd196a409448..076368f6ce540 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113900.rs +++ b/tests/ui/lint/improper-ctypes/ice-normalize.rs @@ -1,5 +1,6 @@ //@ check-pass +// Issue: https://github.com/rust-lang/rust/issues/113900 // Extending `improper_ctypes` to check external-ABI fn-ptrs means that it can encounter // projections which cannot be normalized - unsurprisingly, this shouldn't crash the compiler. diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.rs b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs similarity index 90% rename from tests/ui/lint/improper-ctypes/mustpass-134060.rs rename to tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs index b30be99673687..fb21ad5f7d9df 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.rs +++ b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs @@ -3,6 +3,8 @@ //! comprehensive coverage when the changes are to be relanded, as this is a basic sanity check to //! check that the fuzzed example from #134060 doesn't ICE. +// Issue: https://github.com/rust-lang/rust/issues/134060 + //@ check-pass #![crate_type = "lib"] diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr similarity index 90% rename from tests/ui/lint/improper-ctypes/mustpass-134060.stderr rename to tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr index 9b2de49a7eb51..a9c776eeef88c 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr +++ b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr @@ -1,5 +1,5 @@ warning: `extern` fn uses type `()`, which is not FFI-safe - --> $DIR/mustpass-134060.rs:11:34 + --> $DIR/ice-tykind-coverage.rs:13:34 | LL | extern "C" fn foo_(&self, _: ()) -> i64 { | ^^ not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/mustpass-73251.rs b/tests/ui/lint/improper-ctypes/issue-associated-opaque.rs similarity index 63% rename from tests/ui/lint/improper-ctypes/mustpass-73251.rs rename to tests/ui/lint/improper-ctypes/issue-associated-opaque.rs index 15c1dfcaabf57..3d3f2b195b4eb 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73251.rs +++ b/tests/ui/lint/improper-ctypes/issue-associated-opaque.rs @@ -3,6 +3,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + trait Foo { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/lint-113436-1.rs rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs index 27dcd0184d90f..1fa7a1c7a62f9 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.rs +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs @@ -1,5 +1,8 @@ #![deny(improper_ctypes_definitions)] +// Issue: https://github.com/rust-lang/rust/issues/113436 +// `()` in (fnptr!) return types and ADT fields should be safe + #[repr(C)] pub struct Foo { a: u8, diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-113436-1.stderr rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr index cd7a83a350c33..0279491b530aa 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 this struct = note: `NotSafe` has unspecified layout note: the type is defined here - --> $DIR/lint-113436-1.rs:13:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 | LL | struct NotSafe(u32); | ^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint-113436-1.rs:1:9 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:1:9 | LL | #![deny(improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `Bar`, which is not FFI-safe - --> $DIR/lint-113436-1.rs:22:30 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:25:30 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field note: the type is defined here - --> $DIR/lint-113436-1.rs:16:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:19:1 | LL | pub struct Bar { | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to this struct = note: `NotSafe` has unspecified layout note: the type is defined here - --> $DIR/lint-113436-1.rs:13:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 | LL | struct NotSafe(u32); | ^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs similarity index 78% rename from tests/ui/lint/improper-ctypes/mustpass-113436.rs rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs index 83afaa24d2ef9..1cf4cf18d0269 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113436.rs +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs @@ -1,5 +1,8 @@ //@ check-pass -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes_definitions)] + +// Issue: https://github.com/rust-lang/rust/issues/113436 +// `()` in (fnptr!) return types and ADT fields should be safe #[repr(C)] pub struct Wrap(T); diff --git a/tests/ui/lint/improper-ctypes/mustpass-66202.rs b/tests/ui/lint/improper-ctypes/issue-normalize-return.rs similarity index 87% rename from tests/ui/lint/improper-ctypes/mustpass-66202.rs rename to tests/ui/lint/improper-ctypes/issue-normalize-return.rs index e4cfa54c8d8b8..973c701601845 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-66202.rs +++ b/tests/ui/lint/improper-ctypes/issue-normalize-return.rs @@ -2,6 +2,7 @@ #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/66202 // This test checks that return types are normalized before being checked for FFI-safety, and that // transparent newtype wrappers are FFI-safe if the type being wrapped is FFI-safe. diff --git a/tests/ui/lint/improper-ctypes/lint-73251-1.rs b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs similarity index 66% rename from tests/ui/lint/improper-ctypes/lint-73251-1.rs rename to tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs index 07ae05be69f6c..6fb16b0e83b9e 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-1.rs +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs @@ -1,6 +1,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73251 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73251-1.stderr b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr similarity index 81% rename from tests/ui/lint/improper-ctypes/lint-73251-1.stderr rename to tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr index 749722f0e2203..3f5fff0465009 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-1.stderr +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr @@ -1,12 +1,12 @@ error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73251-1.rs:24:21 + --> $DIR/issue-project-opaque-1.rs:28:21 | LL | fn lint_me() -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73251-1.rs:2:9 + --> $DIR/issue-project-opaque-1.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/lint-73251-2.rs b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/lint-73251-2.rs rename to tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs index c47118672e072..bebb39e128c63 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-2.rs +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs @@ -1,6 +1,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73251 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + pub trait TraitA { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/lint-73251-2.stderr b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr similarity index 81% rename from tests/ui/lint/improper-ctypes/lint-73251-2.stderr rename to tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr index 3770b7d789f67..cc72e1e5b5336 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-2.stderr +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr @@ -1,12 +1,12 @@ error: `extern` block uses type `AliasA`, which is not FFI-safe - --> $DIR/lint-73251-2.rs:38:21 + --> $DIR/issue-project-opaque-2.rs:42:21 | LL | fn lint_me() -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73251-2.rs:2:9 + --> $DIR/issue-project-opaque-2.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 25f52632c6f63..e4c8d25987b9e 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -216,7 +216,7 @@ LL | pub fn no_niche_b(b: Option>); = note: enum has no representation hint error: foreign-code-reachable static uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:146:29 + --> $DIR/lint-ctypes.rs:142:29 | LL | static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; | ^^^^^^^^^^^^ not FFI-safe