diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1f6a382175b7c..98ca8b52692d1 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -336,21 +336,22 @@ lint_implicit_unsafe_autorefs = implicit autoref creates a reference to the dere .method_def = method calls to `{$method_name}` require a reference .suggestion = try using a raw pointer method instead; or if this reference is intentional, make it explicit -lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe +lint_improper_ctypes = {$desc} uses type `{$ty}`, which is not FFI-safe .label = not FFI-safe .note = the type is defined here lint_improper_ctypes_array_help = consider passing a pointer to the array lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe -lint_improper_ctypes_box = box cannot be represented as a single pointer lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead lint_improper_ctypes_char_reason = the `char` type has no C equivalent -lint_improper_ctypes_cstr_help = - consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` +lint_improper_ctypes_cstr_help_const = + consider passing a `*const std::ffi::c_char` instead, converting to/from `{$ty}` as needed +lint_improper_ctypes_cstr_help_mut = + consider passing a `*mut std::ffi::c_char` instead, converting to/from `{$ty}` as needed lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout lint_improper_ctypes_dyn = trait objects have no C equivalent @@ -362,6 +363,7 @@ lint_improper_ctypes_enum_repr_reason = enum has no representation hint lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention + lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants @@ -369,32 +371,41 @@ lint_improper_ctypes_only_phantomdata = composed only of `PhantomData` lint_improper_ctypes_opaque = opaque types have no C equivalent -lint_improper_ctypes_slice_help = consider using a raw pointer instead - +lint_improper_ctypes_slice_help = consider using a raw pointer to the slice's first element (and a length) instead lint_improper_ctypes_slice_reason = slices have no C equivalent -lint_improper_ctypes_str_help = consider using `*const u8` and a length instead +lint_improper_ctypes_str_help = consider using `*const u8` and a length instead lint_improper_ctypes_str_reason = string slices have no C equivalent -lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct -lint_improper_ctypes_struct_fieldless_reason = this struct has no fields -lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct +lint_improper_ctypes_struct_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead +lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field + +lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct +lint_improper_ctypes_struct_fieldless_reason = `{$ty}` has no fields -lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout -lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive -lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields +lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `{$ty}` +lint_improper_ctypes_struct_layout_reason = `{$ty}` has unspecified layout +lint_improper_ctypes_struct_non_exhaustive = `{$ty}` is non-exhaustive +lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields lint_improper_ctypes_tuple_help = consider using a struct instead - lint_improper_ctypes_tuple_reason = tuples have unspecified layout -lint_improper_ctypes_union_fieldless_help = consider adding a member to this union +lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead +lint_improper_ctypes_union_fieldless_help = consider adding a member to this union lint_improper_ctypes_union_fieldless_reason = this union has no fields -lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union +lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` attribute to this union lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance .note = this is dangerous because dereferencing the resulting pointer is undefined behavior .note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index b4c18483a923b..9242a7ab706f2 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -335,6 +335,8 @@ fn register_builtins(store: &mut LintStore) { REFINING_IMPL_TRAIT_INTERNAL ); + add_lint_group!("improper_c_boundaries", IMPROPER_CTYPES_DEFINITIONS, IMPROPER_CTYPES); + add_lint_group!("deprecated_safe", DEPRECATED_SAFE_2024); add_lint_group!( diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c55f2b9dd6f24..c51d2eb2e16ba 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1950,29 +1950,55 @@ pub(crate) enum UnpredictableFunctionPointerComparisonsSuggestion<'a, 'tcx> { }, } +pub(crate) struct ImproperCTypesLayer<'a> { + pub ty: Ty<'a>, + pub inner_ty: Option>, + pub note: DiagMessage, + pub span_note: Option, + pub help: Option, +} + +impl<'a> Subdiagnostic for ImproperCTypesLayer<'a> { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + diag.arg("ty", self.ty); + if let Some(ty) = self.inner_ty { + diag.arg("inner_ty", ty); + } + + if let Some(help) = self.help { + diag.help(diag.eagerly_translate(help)); + } + + diag.note(diag.eagerly_translate(self.note)); + if let Some(note) = self.span_note { + diag.span_note(note, fluent::lint_note); + }; + + diag.remove_arg("ty"); + if self.inner_ty.is_some() { + diag.remove_arg("inner_ty"); + } + } +} + pub(crate) struct ImproperCTypes<'a> { pub ty: Ty<'a>, pub desc: &'a str, pub label: Span, - pub help: Option, - pub note: DiagMessage, - pub span_note: Option, + pub reasons: Vec>, } // Used because of the complexity of Option, DiagMessage, and Option impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> { fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { diag.primary_message(fluent::lint_improper_ctypes); - diag.arg("ty", self.ty); - diag.arg("desc", self.desc); diag.span_label(self.label, fluent::lint_label); - if let Some(help) = self.help { - diag.help(help); - } - diag.note(self.note); - if let Some(note) = self.span_note { - diag.span_note(note, fluent::lint_note); + for reason in self.reasons.into_iter() { + diag.subdiagnostic(reason); } + // declare the arguments at the end to avoid them being clobbered in the subdiagnostics + diag.arg("ty", self.ty); + diag.arg("desc", self.desc); } } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 8ce74ff76effc..7c86d1624f02f 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1,17 +1,19 @@ use std::iter; -use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; +use rustc_abi::{BackendRepr, Size, TagEncoding, Variants, WrappingRange}; use rustc_hir::{Expr, ExprKind, HirId, LangItem}; use rustc_middle::bug; use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, Const, ScalarInt, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::{Span, Symbol, sym}; use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations -pub(crate) use improper_ctypes::ImproperCTypesLint; +pub(crate) use improper_ctypes::{ + IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, ImproperCTypesLint, +}; use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, @@ -705,6 +707,26 @@ pub(crate) fn transparent_newtype_field<'a, 'tcx>( }) } +/// for a given ADT variant, list which fields are non-1ZST +/// (`repr(transparent)` guarantees that there is at most one) +pub(crate) fn map_non_1zst_fields<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + variant: &'a ty::VariantDef, +) -> Vec { + let typing_env = ty::TypingEnv::non_body_analysis(tcx, variant.def_id); + variant + .fields + .iter() + .map(|field| { + let field_ty = tcx.type_of(field.did).instantiate_identity(); + let is_1zst = tcx + .layout_of(typing_env.as_query_input(field_ty)) + .is_ok_and(|layout| layout.is_1zst()); + !is_1zst + }) + .collect() +} + /// Is type known to be non-null? fn ty_is_known_nonnull<'tcx>( tcx: TyCtxt<'tcx>, @@ -842,7 +864,7 @@ fn is_niche_optimization_candidate<'tcx>( /// Check if this enum can be safely exported based on the "nullable pointer optimization". If it /// can, return the type that `ty` can be safely converted to, otherwise return `None`. /// Currently restricted to function pointers, boxes, references, `core::num::NonZero`, -/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. +/// `core::ptr::NonNull`, `#[repr(transparent)]` newtypes, and int-range pattern types. pub(crate) fn repr_nullable_ptr<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, @@ -871,6 +893,14 @@ pub(crate) fn repr_nullable_ptr<'tcx>( _ => return None, }; + if let ty::Pat(base, pat) = field_ty.kind() { + if pattern_has_disallowed_values(*pat) || matches!(base.kind(), ty::Char) { + return get_nullable_type_from_pat(tcx, typing_env, *base, *pat); + } else { + return None; + } + } + if !ty_is_known_nonnull(tcx, typing_env, field_ty) { return None; } @@ -912,6 +942,151 @@ pub(crate) fn repr_nullable_ptr<'tcx>( } } +/// Returns whether a pattern type actually has disallowed values. +pub(crate) fn pattern_has_disallowed_values<'tcx>(pat: ty::Pattern<'tcx>) -> bool { + // note the logic in this function assumes that signed ints use one's complement representation, + // which I believe is a requirement for rust + + /// Find numeric metadata on a pair of range bounds. + /// If None, assume that there are no bounds specified + /// and that this is a usize. in other words, all values are allowed. + fn unwrap_start_end<'tcx>( + start: Const<'tcx>, + end: Const<'tcx>, + ) -> (bool, Size, ScalarInt, ScalarInt) { + let usable_bound = match (start.try_to_value(), end.try_to_value()) { + (Some(ty), _) | (_, Some(ty)) => ty, + (None, None) => bug!( + "pattern range should have at least one defined value: {:?} - {:?}", + start, + end, + ), + }; + let usable_size = usable_bound.valtree.unwrap_leaf().size(); + let is_signed = match usable_bound.ty.kind() { + ty::Int(_) => true, + ty::Uint(_) | ty::Char => false, + kind @ _ => bug!("unexpected non-scalar base for pattern bounds: {:?}", kind), + }; + + let end = match end.try_to_value() { + Some(end) => end.valtree.unwrap_leaf(), + None => { + let max_val = if is_signed { + usable_size.signed_int_max() as u128 + } else { + usable_size.unsigned_int_max() + }; + ScalarInt::try_from_uint(max_val, usable_size).unwrap() + } + }; + let start = match start.try_to_value() { + Some(start) => start.valtree.unwrap_leaf(), + None => { + let min_val = if is_signed { + (usable_size.signed_int_min() as u128) & usable_size.unsigned_int_max() + } else { + 0_u128 + }; + ScalarInt::try_from_uint(min_val, usable_size).unwrap() + } + }; + (is_signed, usable_size, start, end) + } + + match *pat { + ty::PatternKind::NotNull => true, + ty::PatternKind::Range { start, end } => { + let (is_signed, scalar_size, start, end) = unwrap_start_end(start, end); + let (scalar_min, scalar_max) = if is_signed { + ( + (scalar_size.signed_int_min() as u128) & scalar_size.unsigned_int_max(), + scalar_size.signed_int_max() as u128, + ) + } else { + (0, scalar_size.unsigned_int_max()) + }; + + (start.to_bits(scalar_size), end.to_bits(scalar_size)) != (scalar_min, scalar_max) + } + ty::PatternKind::Or(patterns) => { + // first, get a simplified an sorted view of the ranges + let (is_signed, scalar_size, mut ranges) = { + let (is_signed, size, start, end) = match &*patterns[0] { + ty::PatternKind::Range { start, end } => unwrap_start_end(*start, *end), + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + }; + (is_signed, size, vec![(start, end)]) + }; + let scalar_max = if is_signed { + scalar_size.signed_int_max() as u128 + } else { + scalar_size.unsigned_int_max() + }; + ranges.reserve(patterns.len() - 1); + for pat in patterns.iter().skip(1) { + match *pat { + ty::PatternKind::Range { start, end } => { + let (is_this_signed, this_scalar_size, start, end) = + unwrap_start_end(start, end); + assert_eq!(is_signed, is_this_signed); + assert_eq!(scalar_size, this_scalar_size); + ranges.push((start, end)) + } + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + } + } + ranges.sort_by_key(|(start, _end)| { + let is_positive = + if is_signed { start.to_bits(scalar_size) <= scalar_max } else { true }; + (is_positive, start.to_bits(scalar_size)) + }); + + // then, range per range, look at the sizes of the gaps left in between + // (`prev_tail` is the highest value currently accounted for by the ranges, + // unless the first range has not been dealt with yet) + let mut prev_tail = scalar_max; + + for (range_i, (start, end)) in ranges.into_iter().enumerate() { + let (start, end) = (start.to_bits(scalar_size), end.to_bits(scalar_size)); + + // if the start of the current range is lower + // than the current-highest-range-end, ... + let current_range_overlap = + if is_signed && prev_tail > scalar_max && start <= scalar_max { + false + } else if start <= u128::overflowing_add(prev_tail, 1).0 { + range_i > 0 // no overlap possible when dealing with the first range + } else { + false + }; + if current_range_overlap { + // update the current-highest-range-end, if the current range has a higher end + if is_signed { + if prev_tail > scalar_max && end <= scalar_max { + prev_tail = end; + } else if prev_tail <= scalar_max && end > scalar_max { + // nothing to do here + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // no range overlap: there are disallowed values + return true; + } + } + prev_tail != scalar_max + } + } +} + fn get_nullable_type_from_pat<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 2c88397086699..9fa678bb20aac 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -10,28 +10,34 @@ use rustc_hir::intravisit::VisitorExt; use rustc_hir::{self as hir, AmbigArg}; use rustc_middle::bug; use rustc_middle::ty::{ - self, Adt, AdtDef, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, + self, Adt, AdtDef, AdtKind, Binder, FnSig, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, }; use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{Span, sym}; use tracing::debug; use super::repr_nullable_ptr; -use crate::lints::{ImproperCTypes, UsesPowerAlignment}; +use crate::lints::{ImproperCTypes, ImproperCTypesLayer, UsesPowerAlignment}; use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; declare_lint! { /// The `improper_ctypes` lint detects incorrect use of types in foreign /// modules. + /// (In other words, declarations of items defined in foreign code.) + /// This also includes all [`extern` function] pointers. + /// + /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier /// /// ### Example /// /// ```rust /// unsafe extern "C" { /// static STATIC: String; + /// fn some_func(a:String); /// } + /// extern "C" fn register_callback(a: i32, call: extern "C" fn(char)) { /* ... */ } /// ``` /// /// {{produces}} @@ -44,34 +50,40 @@ declare_lint! { /// detects a probable mistake in a definition. The lint usually should /// provide a description of the issue, along with possibly a hint on how /// to resolve it. - IMPROPER_CTYPES, + pub(crate) IMPROPER_CTYPES, Warn, "proper use of libc types in foreign modules" } declare_lint! { /// The `improper_ctypes_definitions` lint detects incorrect use of - /// [`extern` function] definitions. + /// [`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. - IMPROPER_CTYPES_DEFINITIONS, + /// 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" } @@ -134,38 +146,57 @@ declare_lint! { declare_lint_pass!(ImproperCTypesLint => [ IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, - USES_POWER_ALIGNMENT + USES_POWER_ALIGNMENT, ]); -/// Check a variant of a non-exhaustive enum for improper ctypes -/// -/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". -/// This includes linting, on a best-effort basis. There are valid additions that are unlikely. -/// -/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", -/// so we don't need the lint to account for it. -/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. -pub(crate) fn check_non_exhaustive_variant( - non_exhaustive_variant_list: bool, - variant: &ty::VariantDef, -) -> ControlFlow { - // non_exhaustive suggests it is possible that someone might break ABI - // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 - // so warn on complex enums being used outside their crate - if non_exhaustive_variant_list { - // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 - // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` - // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) - if variant_has_complex_ctor(variant) { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive); +type Sig<'tcx> = Binder<'tcx, FnSig<'tcx>>; + +/// Extract (binder-wrapped) FnSig object from a FnPtr's mir::Ty +fn get_fn_sig_from_mir_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Sig<'tcx> { + let ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty); + match *ty.kind() { + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(hdr); + if sig.abi().is_rustic_abi() { + bug!( + "expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one" + ) + } else { + sig + } + } + r @ _ => { + bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not {:?}", r,) } } +} - if variant.field_list_has_applicable_non_exhaustive() { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant); +// FIXME(ctypes): it seems that tests/ui/lint/opaque-ty-ffi-normalization-cycle.rs relies this: +// we consider opaque aliases that normalise to something else to be unsafe. +// ...is it the behaviour we want? +/// a modified version of cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty) +/// so that opaque types prevent normalisation once region erasure occurs +fn erase_and_maybe_normalize<'tcx>(cx: &LateContext<'tcx>, value: Ty<'tcx>) -> Ty<'tcx> { + if (!value.has_aliases()) || value.has_opaque_types() { + cx.tcx.erase_and_anonymize_regions(value) + } else { + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), value).unwrap_or(value) + // note: the code above ^^^ would only cause a call to the commented code below vvv + //let value = cx.tcx.erase_and_anonymize_regions(value); + //let mut folder = TryNormalizeAfterErasingRegionsFolder::new(cx.tcx, cx.typing_env()); + //value.try_fold_with(&mut folder).unwrap_or(value) } +} - ControlFlow::Continue(()) +/// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). +#[inline] +fn get_type_from_field<'tcx>( + cx: &LateContext<'tcx>, + field: &ty::FieldDef, + args: GenericArgsRef<'tcx>, +) -> Ty<'tcx> { + let field_ty = field.ty(cx.tcx, args); + erase_and_maybe_normalize(cx, field_ty) } fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { @@ -178,6 +209,7 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { fn check_arg_for_power_alignment<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { let tcx = cx.tcx; assert!(tcx.sess.target.os == "aix"); + // Structs (under repr(C)) follow the power alignment rule if: // - the first field of the struct is a floating-point type that // is greater than 4-bytes, or @@ -219,22 +251,21 @@ fn check_struct_for_power_alignment<'tcx>( item: &'tcx hir::Item<'tcx>, adt_def: AdtDef<'tcx>, ) { - let tcx = cx.tcx; - // Only consider structs (not enums or unions) on AIX. - if tcx.sess.target.os != "aix" || !adt_def.is_struct() { + if cx.tcx.sess.target.os != "aix" || !adt_def.is_struct() { return; } // The struct must be repr(C), but ignore it if it explicitly specifies its alignment with // either `align(N)` or `packed(N)`. - if adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none() { + debug_assert!(adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none()); + if cx.tcx.sess.target.os == "aix" && !adt_def.all_fields().next().is_none() { let struct_variant_data = item.expect_struct().2; for field_def in struct_variant_data.fields().iter().skip(1) { // Struct fields (after the first field) are checked for the // power alignment rule, as fields after the first are likely // to be the fields that are misaligned. - let ty = tcx.type_of(field_def.def_id).instantiate_identity(); + let ty = cx.tcx.type_of(field_def.def_id).instantiate_identity(); if check_arg_for_power_alignment(cx, ty) { cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment); } @@ -242,16 +273,205 @@ fn check_struct_for_power_alignment<'tcx>( } } -#[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, + /// `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. +#[derive(Clone, Copy)] +enum FnPos { + Arg, + 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. + 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. + fn forbid_phantom(self) -> Self { + match self { + Self::FfiPhantom(ty) => { + Self::new_with_reason(ty, fluent::lint_improper_ctypes_only_phantomdata, None) + } + _ => self, + } + } + + /// Selectively "pluck" some explanations out of a FfiResult::FfiUnsafe, + /// if the note at their core reason is one in a provided list. + /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked, + /// then return FfiSafe. + 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`" + 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 @@ -259,9 +479,161 @@ enum FfiResult<'tcx> { /// in the `FfiResult` is final. type PartialFfiResult<'tcx> = Option>; +/// 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). + Box, + /// Ref (valid non-null pointer, borrows pointee). + Ref, + /// Raw pointer (not necessarily non-null or valid. no info on ownership). + RawPtr, +} + +/// The different ways a given type can have/not have a fixed size. +/// Relies on the vocabulary of the Hierarchy of Sized Traits change (`#![feature(sized_hierarchy)]`) +#[derive(Clone, Copy)] +enum TypeSizedness { + /// Type of definite size (pointers are C-compatible). + Sized, + /// Unsized type because it includes an opaque/foreign type (pointers are C-compatible). + /// (Relies on all Unsized types being `extern` types, and unable to be used in an array/slice) + Unsized, + /// MetaSized types are types whose size can be computed from pointer metadata (slice, string, dyn Trait, closure, ...) + /// (pointers are not C-compatible). + MetaSized, + /// Not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like). + NotYetKnown, +} + +/// Determine if a type is sized or not, and whether it affects references/pointers/boxes to it. +fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { + let tcx = cx.tcx; + + // note that sizedness is unrelated to inhabitedness + if ty.is_sized(tcx, cx.typing_env()) { + TypeSizedness::Sized + } else { + // the overall type is !Sized or ?Sized + match ty.kind() { + ty::Slice(_) | ty::Str | ty::Dynamic(..) => TypeSizedness::MetaSized, + ty::Foreign(..) => TypeSizedness::Unsized, + ty::Adt(def, args) => { + // for now assume: boxes and phantoms don't mess with this + match def.adt_kind() { + AdtKind::Union | AdtKind::Enum => { + bug!("unions and enums are necessarily sized") + } + AdtKind::Struct => { + if let Some(intermediate) = + def.sizedness_constraint(tcx, ty::SizedTraitKind::MetaSized) + { + let ty = intermediate.instantiate(tcx, args); + get_type_sizedness(cx, ty) + } else { + debug_assert!( + def.sizedness_constraint(tcx, ty::SizedTraitKind::Sized).is_some() + ); + TypeSizedness::MetaSized + } + + // if let Some(sym::cstring_type | sym::cstr_type) = + // tcx.get_diagnostic_name(def.did()) + // { + // return TypeSizedness::MetaSized; + // } + + // // note: non-exhaustive structs from other crates are not assumed to be ?Sized + // // for the purpose of sizedness, it seems we are allowed to look at its current contents. + + // if def.non_enum_variant().fields.is_empty() { + // bug!("an empty struct is necessarily sized"); + // } + + // let variant = def.non_enum_variant(); + + // // only the last field may be !Sized (or ?Sized in the case of type params) + // let last_field = match (&variant.fields).iter().last() { + // Some(last_field) => last_field, + // // even nonexhaustive-empty structs from another crate are considered Sized + // // (eventhough one could add a !Sized field to them) + // None => bug!("Empty struct should be Sized, right?"), // + // }; + // let field_ty = get_type_from_field(cx, last_field, args); + // match get_type_sizedness(cx, field_ty) { + // s @ (TypeSizedness::MetaSized + // | TypeSizedness::Unsized + // | TypeSizedness::NotYetKnown) => s, + // TypeSizedness::Sized => { + // bug!("failed to find the reason why struct `{:?}` is unsized", ty) + // } + // } + } + } + } + ty::Tuple(tuple) => { + // only the last field may be !Sized (or ?Sized in the case of type params) + let item_ty: Ty<'tcx> = match tuple.last() { + Some(item_ty) => *item_ty, + None => bug!("Empty tuple (AKA unit type) should be Sized, right?"), + }; + let item_ty = erase_and_maybe_normalize(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::Opaque | ty::Projection | ty::Inherent, ..) => { + return TypeSizedness::NotYetKnown; + } + + ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), + + ty::Alias(ty::Free, ..) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Placeholder(..) + | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + } + } +} + bitflags! { + /// VisitorState flags that are linked with the root type's use. + /// (These are the permanent part of the state, kept when visiting new mir::Ty.) #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct VisitorState: u8 { + struct RootUseFlags: u16 { /// For use in (externally-linked) static variables. const STATIC = 0b000001; /// For use in functions in general. @@ -276,9 +648,61 @@ bitflags! { } } -impl VisitorState { +/// Description of the relationship between current mir::Ty and +/// the type (or lack thereof) immediately containing it +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum OuterTyKind { + None, + /// 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, + /// For arrays/slices but also tuples + OtherItem, +} + +impl OuterTyKind { + /// Computes the relationship by providing the containing mir::Ty itself + fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { + match ty.kind() { + ty::FnPtr(..) => Self::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::Pointee { raw: false, mutable: hir::Mutability::Mut } + } else { + Self::AdtField + } + } + ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::OtherItem, + k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct VisitorState { + /// Flags describing both the overall context in which the current mir::Ty is, + /// linked to how the Visitor's original mir::Ty was used. + root_use_flags: RootUseFlags, + /// Flags describing both the immediate context in which the current mir::Ty is, + /// linked to how it relates to its parent mir::Ty (or lack thereof). + outer_ty_kind: OuterTyKind, + /// Type recursion depth, to prevent infinite recursion + depth: usize, +} + +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 = @@ -291,57 +715,118 @@ impl VisitorState { const RETURN_TY_IN_FNPTR: Self = Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits() | Self::FN_RETURN.bits()) .unwrap(); +} - /// Get the proper visitor state for a given function's arguments. - fn argument_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::ARGUMENT_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::ARGUMENT_TY_IN_DECLARATION, +impl VisitorState { + /// From an existing state, compute the state of any subtype of the current type. + /// (General case.) + fn get_next<'tcx>(&self, current_ty: Ty<'tcx>) -> Self { + assert!(!matches!(current_ty.kind(), ty::FnPtr(..))); + Self { + root_use_flags: self.root_use_flags, + outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), + depth: self.depth + 1, } } - /// Get the proper visitor state for a given function's return type. - fn return_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::RETURN_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::RETURN_TY_IN_DECLARATION, + /// Generate the state for an "outermost" type that needs to be checked + fn entry_point(root_use_flags: RootUseFlags) -> Self { + Self { root_use_flags, outer_ty_kind: OuterTyKind::None, depth: 0 } + } + + /// Get the proper visitor state for a given function's arguments or return type. + fn entry_point_from_fnmode(fn_mode: CItemKind, fn_pos: FnPos) -> Self { + let p_flags = match (fn_mode, fn_pos) { + (CItemKind::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, + (CItemKind::Callback, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_FNPTR, + (CItemKind::ExportedStatic, _) => bug!( + "VisitorState::entry_point_from_fnmode() should not be used for static variables!" + ), + }; + Self::entry_point(p_flags) + } + + /// Get the proper visitor state for a static variable's type + fn static_var() -> Self { + Self::entry_point(RootUseFlags::STATIC_TY) + } + + /// Get the proper visitor state for a locally-defined static variable's type + fn static_var_def() -> Self { + Self::entry_point(RootUseFlags::EXPORTED_STATIC_TY) + } + + /// Whether the type is used as the type of a static variable. + fn is_direct_in_static(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::STATIC); + 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.contains(Self::FUNC); + fn is_in_function(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FUNC); if ret { - debug_assert!(!self.contains(Self::STATIC)); + debug_assert!(!self.root_use_flags.contains(RootUseFlags::STATIC)); } ret } /// Whether the type is used (directly or not) in a function, in return position. - fn is_in_function_return(self) -> bool { - let ret = self.contains(Self::FN_RETURN); + fn is_in_function_return(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FN_RETURN); if ret { debug_assert!(self.is_in_function()); } ret } - /// Whether the type is used (directly or not) in a defined function. - /// In other words, whether or not we allow non-FFI-safe types behind a C pointer, - /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_defined_function(self) -> bool { - self.contains(Self::DEFINED) && self.is_in_function() + + /// Whether the type is directly used in a function, in return position. + fn is_direct_function_return(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::None) && self.is_in_function_return() + } + + /// Whether the type itself is the type of a function argument or return type. + fn is_direct_in_function(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::None) && 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, + /// Whether the type is used (directly or not) in a defined function. + /// In other words, whether or not we allow non-FFI-safe types behind a C pointer, /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_fnptr(self) -> bool { - self.contains(Self::THEORETICAL) && self.is_in_function() + fn is_in_defined_function(&self) -> bool { + self.root_use_flags.contains(RootUseFlags::DEFINED) && self.is_in_function() } /// Whether we can expect type parameters and co in a given type. - fn can_expect_ty_params(self) -> bool { + fn can_expect_ty_params(&self) -> bool { // rust-defined functions, as well as FnPtrs - self.contains(Self::THEORETICAL) || self.is_in_defined_function() + 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) + } + + /// 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, .. }) + } + + /// Whether the current type is behind a raw pointer + fn is_raw_pointee(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::Pointee { raw: true, .. }) + } + + /// Whether the current type directly in the memory layout of the parent ty + fn is_memory_inlined(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::AdtField | OuterTyKind::OtherItem) } } @@ -350,85 +835,466 @@ impl VisitorState { /// and ``visit_*`` methods to recurse. struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, + /// The module id of the item being checked for FFI-safety + mod_id: DefId, /// To prevent problems with recursive types, /// add a types-in-check cache. - cache: FxHashSet>, - /// The original type being checked, before we recursed - /// to any other types it contains. - base_ty: Ty<'tcx>, - base_fn_mode: CItemKind, + ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self { - Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>, mod_id: DefId) -> Self { + Self { cx, mod_id, ty_cache: FxHashSet::default() } + } + + /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here. + fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + if state.is_in_function_return() { + FfiResult::FfiSafe + } else { + let desc = match ty.kind() { + ty::Adt(..) => fluent::lint_improper_ctypes_uninhabited_enum, + ty::Never => fluent::lint_improper_ctypes_uninhabited_never, + r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r), + }; + FfiResult::new_with_reason(ty, desc, None) + } + } + + /// Return the right help for Cstring and Cstr-linked unsafety. + fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + debug_assert!(matches!(ty.kind(), ty::Adt(def, _) + if matches!( + self.cx.tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + )); + + let help = if state.is_nonmut_pointee() { + fluent::lint_improper_ctypes_cstr_help_const + } else { + fluent::lint_improper_ctypes_cstr_help_mut + }; + + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help)) } - /// Checks if the given field's type is "ffi-safe". - fn check_field_type_for_ffi( + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". + fn visit_indirection( &mut self, state: VisitorState, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, + ty: Ty<'tcx>, + inner_ty: Ty<'tcx>, + indirection_kind: IndirectionKind, ) -> FfiResult<'tcx> { - let field_ty = field.ty(self.cx.tcx, args); - let field_ty = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), field_ty) - .unwrap_or(field_ty); - self.visit_type(state, field_ty) + let tcx = self.cx.tcx; + + if let ty::Adt(def, _) = inner_ty.kind() { + if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) = + tcx.get_diagnostic_name(def.did()) + { + // we have better error messages when checking for C-strings directly + let mut cstr_res = self.visit_cstr(state.get_next(ty), inner_ty); // always unsafe with one depth-one reason. + + // Cstr pointer have metadata, CString is Sized + if diag_name == sym::cstr_type { + // we need to override the "type" part of `cstr_res`'s only FfiResultReason + // so it says that it's the use of the indirection that is unsafe + match cstr_res { + FfiResult::FfiUnsafe(ref mut reasons) => { + reasons.first_mut().unwrap().reason.ty = ty; + } + _ => unreachable!(), + } + let note = match indirection_kind { + IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box, + }; + return cstr_res.wrap_all(ty, note, None); + } else { + return cstr_res; + } + } + } + + // 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 + } + TypeSizedness::NotYetKnown => { + // types with sizedness NotYetKnown: + // - Type params (with `variable: impl Trait` shorthand or not) + // (function definitions only, let's see how this interacts with monomorphisation) + // - Self in trait functions/methods + // - Opaque return types + // (always FFI-unsafe) + // - non-exhaustive structs/enums/unions from other crates + // (always FFI-unsafe) + // (for the three first, this is unless there is a `+Sized` bound involved) + + // whether they are FFI-safe or not does not depend on the indirections involved (&Self, &T, Box), + // so let's not wrap the current context around a potential FfiUnsafe type param. + self.visit_type(state.get_next(ty), inner_ty) + } + TypeSizedness::MetaSized => { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + _ => None, + }; + let reason = match indirection_kind { + IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box, + }; + return FfiResult::new_with_reason(ty, reason, help); + } + } } /// Checks if the given `VariantDef`'s field types are "ffi-safe". - fn check_variant_for_ffi( + fn visit_variant_fields( &mut self, state: VisitorState, ty: Ty<'tcx>, - def: ty::AdtDef<'tcx>, + def: AdtDef<'tcx>, variant: &ty::VariantDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; - let transparent_with_all_zst_fields = if def.repr().transparent() { - if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { - // Transparent newtypes have at most one non-ZST field which needs to be checked.. - match self.check_field_type_for_ffi(state, field, args) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - false + 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 !ty.is_inhabited_from(self.cx.tcx, self.mod_id, self.cx.typing_env()) { + // let's consider transparent structs to be maybe unsafe if uninhabited, + // even if that is because of fields otherwise ignored in FFI-safety checks + ffires_accumulator += variant + .fields + .iter() + .map(|field| { + let field_ty = get_type_from_field(self.cx, field, args); + let mut field_res = self.visit_type(state.get_next(ty), field_ty); + field_res.take_with_core_note(&[ + fluent::lint_improper_ctypes_uninhabited_enum, + fluent::lint_improper_ctypes_uninhabited_never, + ]) + }) + .reduce(|r1, r2| r1 + r2) + .unwrap() // if uninhabited, then >0 fields + } + if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { + // Transparent newtypes have at most one non-ZST field which needs to be checked later + (false, vec![field]) + } 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 { - all_phantom &= match self.check_field_type_for_ffi(state, field, args) { - FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } if ty.is_unit() => false, + let mut fields_ok_list = vec![true; field_list.len()]; + + for (field_i, field) in field_list.into_iter().enumerate() { + let field_ty = get_type_from_field(self.cx, field, args); + let ffi_res = self.visit_type(state.get_next(ty), field_ty); + + // checking that this is not an FfiUnsafe due to an unit type: + // visit_type should be smart enough to not consider it unsafe if called from another ADT + #[cfg(debug_assertions)] + if let FfiUnsafe(ref reasons) = ffi_res { + if let (1, Some(FfiUnsafeExplanation { reason, .. })) = + (reasons.len(), reasons.first()) + { + let FfiUnsafeReason { ty, .. } = reason.as_ref(); + debug_assert!(!ty.is_unit()); + } + } + + all_phantom &= match ffi_res { FfiPhantom(..) => true, - r @ FfiUnsafe { .. } => return r, + FfiSafe => false, + r @ FfiUnsafe { .. } => { + 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().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( + ty, + fluent::lint_improper_ctypes_struct_dueto, + None, + ); + if transparent_with_all_zst_fields { + field_ffires + + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_struct_zst, + None, + ) + } else { + field_ffires + } + } else { + // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields, + // but also exactly one non-1ZST field that is FFI-safe: + // we want to suggest repr(transparent) here. + // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises) + let non_1zst_fields = super::map_non_1zst_fields(self.cx.tcx, variant); + let (last_non_1zst, non_1zst_count) = non_1zst_fields.into_iter().enumerate().fold( + (None, 0_usize), + |(prev_nz, count), (field_i, is_nz)| { + if is_nz { (Some(field_i), count + 1) } else { (prev_nz, count) } + }, + ); + let help = if non_1zst_count == 1 + && last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true) + { + match def.adt_kind() { + AdtKind::Struct => { + Some(fluent::lint_improper_ctypes_struct_consider_transparent) + } + AdtKind::Union => { + Some(fluent::lint_improper_ctypes_union_consider_transparent) + } + AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"), + } + } else { + None + }; + + FfiUnsafe(explanations).wrap_all( + ty, + fluent::lint_improper_ctypes_struct_dueto, + help, + ) + } + } else if all_phantom { FfiPhantom(ty) } else if transparent_with_all_zst_fields { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_struct_zst, None) } else { FfiSafe } } + fn visit_struct_or_union( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); + + if !((def.repr().c() && !def.repr().packed()) || def.repr().transparent()) { + // FIXME(ctypes) packed reprs prevent C compatibility, right? + return FfiResult::new_with_reason( + ty, + if def.is_struct() { + fluent::lint_improper_ctypes_struct_layout_reason + } else { + fluent::lint_improper_ctypes_union_layout_reason + }, + if def.is_struct() { + Some(fluent::lint_improper_ctypes_struct_layout_help) + } else { + // FIXME(#60405): confirm that this makes sense for unions once #60405 / RFC2645 stabilises + Some(fluent::lint_improper_ctypes_union_layout_help) + }, + ); + } + + if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { + return FfiResult::new_with_reason( + ty, + if def.is_struct() { + fluent::lint_improper_ctypes_struct_non_exhaustive + } else { + fluent::lint_improper_ctypes_union_non_exhaustive + }, + None, + ); + } + + let ffires = if def.non_enum_variant().fields.is_empty() { + FfiResult::new_with_reason( + ty, + if def.is_struct() { + fluent::lint_improper_ctypes_struct_fieldless_reason + } else { + fluent::lint_improper_ctypes_union_fieldless_reason + }, + if def.is_struct() { + Some(fluent::lint_improper_ctypes_struct_fieldless_help) + } else { + Some(fluent::lint_improper_ctypes_union_fieldless_help) + }, + ) + } else { + self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) + }; + + // 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( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Enum)); + use FfiResult::*; + + if def.variants().is_empty() { + // Empty enums are implicitly handled as the never type: + return self.visit_uninhabited(state, ty); + } + // Check for a repr() attribute to specify the size of the + // discriminant. + if !(def.repr().c() && !def.repr().packed()) + && !def.repr().transparent() + && def.repr().int.is_none() + { + // Special-case types like `Option` and `Result` + if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { + return self.visit_type(state.get_next(ty), inner_ty); + } + + return FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_enum_repr_reason, + Some(fluent::lint_improper_ctypes_enum_repr_help), + ); + } + + // FIXME(ctypes): connect `def.repr().int` to visit_numeric + // (for now it's OK, `repr(char)` doesn't exist and visit_numeric doesn't warn on anything else) + + let enum_non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); + // Check the contained variants. + + // 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 improper_on_nonexhaustive_flag { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_non_exhaustive, None) + } else if nonexhaustive_variant_flag { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_non_exhaustive_variant, + None, + ) + } else { + // 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() + .enumerate() + .map(|(variant_i, variant)| { + let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args); + variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[ + fluent::lint_improper_ctypes_uninhabited_enum, + fluent::lint_improper_ctypes_uninhabited_never, + ]); + // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms? + // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?) + variant_res.forbid_phantom() + }) + .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``) + ffires.with_overrides(Some(ty)) + } + } + /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { @@ -438,179 +1304,101 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Protect against infinite recursion, for example // `struct S(*mut S);`. - // FIXME: A recursion limit is necessary as well, for irregular - // recursive types. - if !self.cache.insert(ty) { + if !(self.ty_cache.insert(ty) + && self.cx.tcx.recursion_limit().value_within_limit(state.depth)) + { return FfiSafe; } match *ty.kind() { ty::Adt(def, args) => { - if let Some(boxed) = ty.boxed_ty() - && ( - // FIXME(ctypes): this logic is broken, but it still fits the current tests - state.is_in_defined_function() - || (state.is_in_fnptr() - && matches!(self.base_fn_mode, CItemKind::Definition)) - ) - { - if boxed.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; - } else { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_box, - help: None, - }; - } + if let Some(inner_ty) = ty.boxed_ty() { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Box); } if def.is_phantom_data() { return FfiPhantom(ty); } 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()) - && !self.base_ty.is_mutable_ptr() { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_cstr_reason, - help: Some(fluent::lint_improper_ctypes_cstr_help), - }; - } - - if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_layout_reason - } else { - fluent::lint_improper_ctypes_union_layout_reason - }, - help: if def.is_struct() { - Some(fluent::lint_improper_ctypes_struct_layout_help) - } else { - Some(fluent::lint_improper_ctypes_union_layout_help) - }, - }; - } - - if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_non_exhaustive - } else { - fluent::lint_improper_ctypes_union_non_exhaustive - }, - help: None, - }; - } - - if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_fieldless_reason - } else { - fluent::lint_improper_ctypes_union_fieldless_reason - }, - help: if def.is_struct() { - Some(fluent::lint_improper_ctypes_struct_fieldless_help) - } else { - Some(fluent::lint_improper_ctypes_union_fieldless_help) - }, - }; - } - - self.check_variant_for_ffi(state, ty, def, def.non_enum_variant(), args) - } - AdtKind::Enum => { - if def.variants().is_empty() { - // Empty enums are okay... although sort of useless. - return FfiSafe; - } - // Check for a repr() attribute to specify the size of the - // discriminant. - if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() - { - // Special-case types like `Option` and `Result` - if let Some(ty) = - repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) - { - return self.visit_type(state, ty); - } - - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; - } - - let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); - // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; - - match self.check_variant_for_ffi(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } - }); - if let ControlFlow::Break(result) = ret { - return result; + return self.visit_cstr(state, ty); } - - FfiSafe + self.visit_struct_or_union(state, ty, def, args) } + AdtKind::Enum => self.visit_enum(state, ty, def, args), } } - ty::Char => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_char_reason, - help: Some(fluent::lint_improper_ctypes_char_help), - }, - - // It's just extra invariants on the type that you need to uphold, + // Pattern types are just extra invariants on the type that you need to uphold, // but only the base type is relevant for being representable in FFI. - ty::Pat(base, ..) => self.visit_type(state, base), + // (note: this lint was written when pattern types could only be integers constrained to ranges) + // (also note: the lack of ".get_next(ty)" on the state is on purpose) + ty::Pat(pat_ty, _) => self.visit_type(state, pat_ty), - // Primitive types with a stable representation. - ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, + // types which likely have a stable representation, if the target architecture defines those + // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 + ty::Int(..) | ty::Uint(..) | ty::Float(..) => FfiResult::FfiSafe, - ty::Slice(_) => FfiUnsafe { + ty::Bool => FfiResult::FfiSafe, + + ty::Char => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_slice_reason, - help: Some(fluent::lint_improper_ctypes_slice_help), - }, + fluent::lint_improper_ctypes_char_reason, + Some(fluent::lint_improper_ctypes_char_help), + ), + + ty::Slice(inner_ty) => { + // ty::Slice is used for !Sized arrays, since they are the pointee for actual slices + let slice_is_actually_array = + state.is_memory_inlined() || state.is_direct_in_static(); + + if slice_is_actually_array { + self.visit_type(state.get_next(ty), inner_ty) + } else { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_slice_reason, + Some(fluent::lint_improper_ctypes_slice_help), + ) + } + } ty::Dynamic(..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_dyn, None) } - ty::Str => FfiUnsafe { + ty::Str => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_str_reason, - help: Some(fluent::lint_improper_ctypes_str_help), - }, + fluent::lint_improper_ctypes_str_reason, + Some(fluent::lint_improper_ctypes_str_help), + ), - ty::Tuple(..) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - }, - - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) - if { - (state.is_in_defined_function() || state.is_in_fnptr()) - && ty.is_sized(self.cx.tcx, self.cx.typing_env()) - } => - { - FfiSafe + ty::Tuple(tuple) => { + if tuple.is_empty() + && ( + // C functions can return void + state.is_direct_function_return() + // `()` fields are safe + || state.is_field() + // this serves as a "void*" + || state.is_raw_pointee() + ) + { + FfiSafe + } else { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_tuple_reason, + Some(fluent::lint_improper_ctypes_tuple_help), + ) + } } ty::RawPtr(ty, _) @@ -622,57 +1410,96 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.visit_type(state, ty), + ty::RawPtr(inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::RawPtr); + } + ty::Ref(_, inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Ref); + } - ty::Array(inner_ty, _) => self.visit_type(state, inner_ty), + ty::Array(inner_ty, _) => { + if state.is_direct_in_function() { + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_array_reason, + Some(fluent::lint_improper_ctypes_array_help), + ) + } else { + // let's allow phantoms to go through, + // since an array of 1-ZSTs is also a 1-ZST + self.visit_type(state.get_next(ty), inner_ty) + } + } + // 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 FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_fnptr_reason, - help: Some(fluent::lint_improper_ctypes_fnptr_help), - }; - } - - let sig = tcx.instantiate_bound_regions_with_erased(sig); - for arg in sig.inputs() { - match self.visit_type(VisitorState::ARGUMENT_TY_IN_FNPTR, *arg) { - FfiSafe => {} - r => return r, - } - } - - let ret_ty = sig.output(); - if ret_ty.is_unit() { - return FfiSafe; + fluent::lint_improper_ctypes_fnptr_reason, + Some(fluent::lint_improper_ctypes_fnptr_help), + ) + } else { + FfiSafe } - - self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, ret_ty) } ty::Foreign(..) => FfiSafe, - // While opaque types are checked for earlier, if a projection in a struct field - // normalizes to an opaque type, then it will reach this branch. + ty::Never => self.visit_uninhabited(state, ty), + + // This is only half of the checking-for-opaque-aliases story: + // since they are liable to vanish on normalisation, we need a specific to find them through + // other aliases, which is called in the next branch of this `match ty.kind()` statement ty::Alias(ty::Opaque, ..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None) } - // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, + // `extern "C" fn` function definitions can have type parameters, which may or may not be FFI-safe, // so they are currently ignored for the purposes of this lint. - ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..) - if state.can_expect_ty_params() => - { - FfiSafe + // function pointers can do the same + // + // however, these ty_kind:s can also be encountered because the type isn't normalized yet. + ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) => { + if ty.has_opaque_types() { + // FIXME(ctypes): this is suboptimal because we give up + // on reporting anything *else* than the opaque part of the type + // but this is better than not reporting anything, or crashing + self.visit_for_opaque_ty(ty).unwrap() + } else { + // in theory, thanks to erase_and_maybe_normalize, + // normalisation has already occurred + debug_assert_eq!( + self.cx + .tcx + .try_normalize_erasing_regions(self.cx.typing_env(), ty,) + .unwrap_or(ty), + ty, + ); + + if matches!( + ty.kind(), + ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..) + ) && state.can_expect_ty_params() + { + FfiSafe + } else { + // ty::Alias(ty::Free), and all params/aliases for something + // defined beyond the FFI boundary + bug!("unexpected type in foreign function: {:?}", ty) + } + } } ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), - ty::Param(..) - | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) - | ty::Infer(..) + ty::Infer(..) | ty::Bound(..) | ty::Error(_) | ty::Closure(..) @@ -702,61 +1529,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - if let Some(ty) = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), ty) - .unwrap_or(ty) - .visit_with(&mut ProhibitOpaqueTypes) + ty.visit_with(&mut ProhibitOpaqueTypes) .break_value() - { - Some(FfiResult::FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_opaque, - help: None, - }) - } else { - None - } + .map(|ty| FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None)) } - /// Check if the type is array and emit an unsafe type lint. - fn check_for_array_ty(&mut self, ty: Ty<'tcx>) -> PartialFfiResult<'tcx> { - if let ty::Array(..) = ty.kind() { - Some(FfiResult::FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - }) - } else { - None - } - } - - /// Determine the FFI-safety of a single (MIR) type, given the context of how it is used. fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { - if let Some(res) = self.visit_for_opaque_ty(ty) { - return res; - } - - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); - - // C doesn't really support passing arrays by value - the only way to pass an array by value - // is through a struct. So, first test that the top level isn't an array, and then - // recursively check the types inside. - if state.is_in_function() { - if let Some(res) = self.check_for_array_ty(ty) { - return res; - } - } - - // Don't report FFI errors for unit return types. This check exists here, and not in - // the caller (where it would make more sense) so that normalization has definitely - // happened. - if state.is_in_function_return() && ty.is_unit() { - return FfiResult::FfiSafe; - } - + let ty = erase_and_maybe_normalize(self.cx, ty); self.visit_type(state, ty) } } @@ -767,26 +1546,31 @@ 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> { - spans: Vec, + current_depth: usize, + depths: Vec, + decls: Vec<&'tcx hir::FnDecl<'tcx>>, + hir_ids: Vec, 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); - if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind + self.current_depth += 1; + if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, decl, .. }) = ty.kind && !abi.is_rustic_abi() { - self.spans.push(ty.span); + self.decls.push(*decl); + self.depths.push(self.current_depth); + self.hir_ids.push(ty.hir_id); } - hir::intravisit::walk_ty(self, ty) + hir::intravisit::walk_ty(self, ty); + self.current_depth -= 1; } } @@ -804,17 +1588,26 @@ impl<'tcx> ImproperCTypesLint { } } - let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() }; + let mut visitor = FnPtrFinder { + hir_ids: Vec::new(), + tys: Vec::new(), + decls: Vec::new(), + depths: Vec::new(), + current_depth: 0, + }; ty.visit_with(&mut visitor); visitor.visit_ty_unambig(hir_ty); - let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)); - for (fn_ptr_ty, span) in all_types { - let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode); - // FIXME(ctypes): make a check_for_fnptr - let ffi_res = visitor.check_type(state, fn_ptr_ty); + let all_types = iter::zip( + iter::zip(visitor.depths.drain(..), visitor.hir_ids.drain(..)), + iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)), + ); + + for ((depth, hir_id), (fn_ptr_ty, decl)) in all_types { + let mir_sig = get_fn_sig_from_mir_ty(cx, fn_ptr_ty); + let mod_id = cx.tcx.parent_module(hir_id).to_def_id(); - self.process_ffi_result(cx, span, ffi_res, fn_mode); + self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, mod_id, depth); } } @@ -823,7 +1616,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<'_>, ) { @@ -831,13 +1623,11 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, 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::return_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); + self.check_type_for_external_abi_fnptr(cx, ret_hir, sig.output()); } } @@ -858,11 +1648,22 @@ impl<'tcx> ImproperCTypesLint { check_struct_for_power_alignment(cx, item, adt_def); } - fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { - let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::Declaration); - let ffi_res = visitor.check_type(VisitorState::STATIC_TY, ty); - self.process_ffi_result(cx, span, ffi_res, CItemKind::Declaration); + /// Check that an extern "ABI" static variable is of a ffi-safe type. + fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id).to_def_id(); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); + let ffi_res = visitor.check_type(VisitorState::static_var(), ty); + self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); + } + + /// Check that a `#[no_mangle]`/`#[export_name = _]` static variable is of a ffi-safe type. + fn check_exported_static(&self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id).to_def_id(); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); + let ffi_res = visitor.check_type(VisitorState::static_var_def(), ty); + self.process_ffi_result(cx, span, ffi_res, CItemKind::ExportedStatic); } /// Check if a function's argument types and result type are "ffi-safe". @@ -870,22 +1671,25 @@ impl<'tcx> ImproperCTypesLint { &mut self, cx: &LateContext<'tcx>, fn_mode: CItemKind, - def_id: LocalDefId, + sig: Sig<'tcx>, decl: &'tcx hir::FnDecl<'_>, + mod_id: DefId, + depth: usize, ) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); + let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, *input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); + let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -903,15 +1707,53 @@ impl<'tcx> ImproperCTypesLint { FfiResult::FfiPhantom(ty) => { self.emit_ffi_unsafe_type_lint( cx, - ty, + ty.clone(), sp, - fluent::lint_improper_ctypes_only_phantomdata, - None, + vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + help: None, + inner_ty: None, + }], fn_mode, ); } - FfiResult::FfiUnsafe { ty, reason, help } => { - self.emit_ffi_unsafe_type_lint(cx, ty, sp, reason, help, fn_mode); + FfiResult::FfiUnsafe(explanations) => { + for explanation in explanations { + let mut ffiresult_recursor = ControlFlow::Continue(explanation.reason.as_ref()); + let mut cimproper_layers: Vec> = vec![]; + + // this whole while block converts the arbitrarily-deep + // FfiResult stack to an ImproperCTypesLayer Vec + while let ControlFlow::Continue(FfiUnsafeReason { ty, note, help, inner }) = + ffiresult_recursor + { + if let Some(layer) = cimproper_layers.last_mut() { + layer.inner_ty = Some(ty.clone()); + } + cimproper_layers.push(ImproperCTypesLayer { + ty: ty.clone(), + inner_ty: None, + help: help.clone(), + note: note.clone(), + span_note: None, // filled later + }); + + if let Some(inner) = inner { + ffiresult_recursor = ControlFlow::Continue(inner.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } + } + let cause_ty = if let Some(cause_ty) = explanation.override_cause_ty { + cause_ty + } else { + // should always have at least one type + cimproper_layers.last().unwrap().ty.clone() + }; + self.emit_ffi_unsafe_type_lint(cx, cause_ty, sp, cimproper_layers, fn_mode); + } } } } @@ -921,36 +1763,52 @@ 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 { - 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, + // Same thing with static variables, which are "FFI-exported" + CItemKind::ExportedStatic => IMPROPER_CTYPES_DEFINITIONS, }; let desc = match fn_mode { - 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 + CItemKind::ImportedExtern => "`extern` block", + CItemKind::ExportedFunction => "`extern` fn", + CItemKind::ExportedStatic => "foreign-code-reachable static", + CItemKind::Callback => "`extern` callback", }; - 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 }); } } -/// `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()); @@ -960,19 +1818,22 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // fnptrs are a special case, they always need to be treated as // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline + self.check_fn_for_external_abi_fnptr(cx, it.owner_id.def_id, sig.decl); + let mir_sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(it.owner_id.def_id).to_def_id(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::Declaration, it.owner_id.def_id, sig.decl); - } else { - self.check_fn_for_external_abi_fnptr( + self.check_foreign_fn( cx, - CItemKind::Declaration, - it.owner_id.def_id, + CItemKind::ImportedExtern, + mir_sig, sig.decl, + mod_id, + 0, ); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { - self.check_foreign_static(cx, it.owner_id, ty.span); + self.check_foreign_static(cx, it.hir_id(), ty.span); } hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), } @@ -985,11 +1846,28 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { | hir::ItemKind::TyAlias(_, _, ty) => { self.check_type_for_external_abi_fnptr( cx, - VisitorState::STATIC_TY, ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), - CItemKind::Definition, ); + + // FIXME: cx.tcx.has_attr no worky + // if matches!(item.kind, hir::ItemKind::Static(..)) + // && (cx.tcx.has_attr(item.owner_id, sym::no_mangle) + // || cx.tcx.has_attr(item.owner_id, sym::export_name)) + if matches!(item.kind, hir::ItemKind::Static(..)) { + let is_exported_static = cx.tcx.get_all_attrs(item.owner_id).iter().any(|x| { + matches!( + x, + hir::Attribute::Parsed( + hir::attrs::AttributeKind::NoMangle(_) + | hir::attrs::AttributeKind::ExportName { .. } + ) + ) + }); + if is_exported_static { + self.check_exported_static(cx, item.hir_id(), ty.span); + } + } } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks hir::ItemKind::Fn { .. } => {} @@ -1019,10 +1897,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_TY, field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), - CItemKind::Definition, ); } @@ -1046,10 +1922,68 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // fnptrs are a special case, they always need to be treated as // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline + self.check_fn_for_external_abi_fnptr(cx, id, decl); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::Definition, id, decl); - } else { - self.check_fn_for_external_abi_fnptr(cx, CItemKind::Definition, id, decl); + let mir_sig = cx.tcx.fn_sig(id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(id).to_def_id(); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, mod_id, 0); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, tr_it: &hir::TraitItem<'tcx>) { + match tr_it.kind { + hir::TraitItemKind::Const(hir_ty, _) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + hir::TraitItemKind::Fn(sig, trait_fn) => { + match trait_fn { + // if the method is defined here, + // there is a matching ``LateLintPass::check_fn`` call, + // let's not redo that work + hir::TraitFn::Provided(_) => return, + hir::TraitFn::Required(_) => (), + } + let local_id = tr_it.owner_id.def_id; + + self.check_fn_for_external_abi_fnptr(cx, local_id, sig.decl); + if !sig.header.abi.is_rustic_abi() { + let mir_sig = cx.tcx.fn_sig(local_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(local_id).to_def_id(); + self.check_foreign_fn( + cx, + CItemKind::ExportedFunction, + mir_sig, + sig.decl, + mod_id, + 0, + ); + } + } + hir::TraitItemKind::Type(_, ty_maybe) => { + if let Some(hir_ty) = ty_maybe { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + } + } + } + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, im_it: &hir::ImplItem<'tcx>) { + // note: we do not skip these checks eventhough they might generate dupe warnings because: + // - the corresponding trait might be in another crate + // - the corresponding trait might have some templating involved, so only the impl has the full type information + match im_it.kind { + hir::ImplItemKind::Type(hir_ty) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + hir::ImplItemKind::Fn(_sig, _) => { + // see ``LateLintPass::check_fn`` + } + hir::ImplItemKind::Const(hir_ty, _) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } } } } 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/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/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index 4bf04dd392341..7d92ec474179a 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -99,7 +99,6 @@ extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm32-unknown: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () -#[allow(improper_ctypes_definitions)] #[no_mangle] #[unsafe(naked)] extern "C" fn fn_i128_i128(num: i128) -> i128 { diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..da5e56da937c6 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -29,7 +29,7 @@ asm_experimental_arch, unboxed_closures )] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] #![no_std] #![no_core] diff --git a/tests/crashes/130310.rs b/tests/crashes/130310.rs deleted file mode 100644 index d59dd39983c78..0000000000000 --- a/tests/crashes/130310.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ known-bug: rust-lang/rust#130310 - -use std::marker::PhantomData; - -#[repr(C)] -struct A { - a: *const A>, - p: PhantomData, -} - -extern "C" { - fn f(a: *const A<()>); -} - -fn main() {} diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index 4ffc81eb5f6f5..029de4a2676ef 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -63,7 +63,8 @@ #![feature(no_core, rustc_attrs, lang_items)] #![feature(unsized_fn_params, transparent_unions)] #![no_core] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] +#![allow(improper_ctypes_definitions, improper_ctypes)] // FIXME: some targets are broken in various ways. // Hence there are `cfg` throughout this test to disable parts of it on those targets. diff --git a/tests/ui/abi/extern/extern-pass-empty.rs b/tests/ui/abi/extern/extern-pass-empty.rs index 1ad52b128ad93..f38f76166bf27 100644 --- a/tests/ui/abi/extern/extern-pass-empty.rs +++ b/tests/ui/abi/extern/extern-pass-empty.rs @@ -1,5 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. +#![allow(improper_ctypes)] +// FIXME: this test is inherently not FFI-safe. // Test a foreign function that accepts empty struct. diff --git a/tests/ui/abi/foreign/foreign-fn-with-byval.rs b/tests/ui/abi/foreign/foreign-fn-with-byval.rs index 9908ec2d2c01a..dbf80385e15f7 100644 --- a/tests/ui/abi/foreign/foreign-fn-with-byval.rs +++ b/tests/ui/abi/foreign/foreign-fn-with-byval.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(improper_ctypes, improper_ctypes_definitions)] +#![allow(improper_ctypes)] #[derive(Copy, Clone)] pub struct S { diff --git a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs index 7d21307e1b2d9..0dfe91b95dd68 100644 --- a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs +++ b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes_definitions, improper_ctypes)] #![feature(unsized_fn_params)] #![crate_type = "lib"] diff --git a/tests/ui/asm/naked-functions-ffi.stderr b/tests/ui/asm/naked-functions-ffi.stderr index f7893a3b8de98..ed31959e18565 100644 --- a/tests/ui/asm/naked-functions-ffi.stderr +++ b/tests/ui/asm/naked-functions-ffi.stderr @@ -6,7 +6,7 @@ LL | pub extern "C" fn naked(p: char) -> u128 { | = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs index 77347b04ede83..b5824243215c4 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs @@ -31,7 +31,7 @@ pub fn test( ) { } -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] struct Test { u128: extern "cmse-nonsecure-call" fn() -> u128, //~ ERROR [E0798] i128: extern "cmse-nonsecure-call" fn() -> i128, //~ ERROR [E0798] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs index 419d26875bcd9..1a261f5df2af7 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs @@ -25,7 +25,7 @@ pub enum ReprTransparentEnumU64 { pub struct U32Compound(u16, u16); #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] pub fn params( f1: extern "cmse-nonsecure-call" fn(), f2: extern "cmse-nonsecure-call" fn(u32, u32, u32, u32), @@ -38,6 +38,7 @@ pub fn params( } #[no_mangle] +#[allow(improper_ctypes)] pub fn returns( f1: extern "cmse-nonsecure-call" fn() -> u32, f2: extern "cmse-nonsecure-call" fn() -> u64, diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index 33d295f7ebe19..8e037a9b413fd 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs @@ -5,10 +5,10 @@ //@ normalize-stderr: "\[u8\]" -> "[i8 or u8 (arch dependant)]" type Foo = extern "C" fn(::std::ffi::CStr); -//~^ WARN `extern` fn uses type +//~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); - //~^ WARN `extern` block uses type + // ^ FIXME: the error isn't seen here but at least it's reported elsewhere } fn main() { diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr index 044c1ae2dd42f..1cb2ec1884080 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -1,22 +1,12 @@ -warning: `extern` fn uses type `CStr`, which is not FFI-safe - --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:12 +warning: `extern` callback uses type `CStr`, which is not FFI-safe + --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:26 | LL | type Foo = extern "C" fn(::std::ffi::CStr); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = 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_definitions)]` on by default + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: `extern` block uses type `CStr`, which is not FFI-safe - --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:10:18 - | -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 - = note: `#[warn(improper_ctypes)]` on by default - -warning: 2 warnings emitted +warning: 1 warning emitted diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 0908d6199efb8..fa300ba9d173b 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -1,13 +1,13 @@ // 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); #[no_mangle] pub extern "C" fn register_something(bind: ExternCallback) -> Struct { -//~^ WARN `extern` fn uses type `str`, which is not FFI-safe +// ^ FIXME: the error isn't seen here, but at least it's reported elsewhere //~^^ WARN `extern` fn uses type `Struct`, which is not FFI-safe Struct(bind) } diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index ebd6cec6ecd3f..d448f6b26f9a6 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -1,21 +1,12 @@ -warning: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/extern-C-str-arg-ice-80125.rs:3:23 +warning: `extern` callback uses type `str`, which is not FFI-safe + --> $DIR/extern-C-str-arg-ice-80125.rs:3:53 | LL | type ExternCallback = extern "C" fn(*const u8, u32, str); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` 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 + | ^^^ 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 `Struct`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:63 @@ -23,13 +14,14 @@ warning: `extern` fn uses type `Struct`, which is not FFI-safe LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Struct` + = note: `Struct` has unspecified layout note: the type is defined here --> $DIR/extern-C-str-arg-ice-80125.rs:6:1 | 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/extern/issue-16250.stderr b/tests/ui/extern/issue-16250.stderr index 9d3e88114616b..f2a3dfc1e8eb3 100644 --- a/tests/ui/extern/issue-16250.stderr +++ b/tests/ui/extern/issue-16250.stderr @@ -4,8 +4,8 @@ error: `extern` block uses type `Foo`, which is not FFI-safe LL | pub fn foo(x: (Foo)); | ^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Foo` + = note: `Foo` has unspecified layout note: the type is defined here --> $DIR/issue-16250.rs:3:1 | diff --git a/tests/ui/hashmap/hashmap-memory.rs b/tests/ui/hashmap/hashmap-memory.rs index 6db5d2e7bef35..7084e4bab3ff3 100644 --- a/tests/ui/hashmap/hashmap-memory.rs +++ b/tests/ui/hashmap/hashmap-memory.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes)] #![allow(non_camel_case_types)] #![allow(dead_code)] #![allow(unused_mut)] diff --git a/tests/ui/issues/issue-51907.rs b/tests/ui/issues/issue-51907.rs index bf3f629df4970..808064fa02240 100644 --- a/tests/ui/issues/issue-51907.rs +++ b/tests/ui/issues/issue-51907.rs @@ -1,6 +1,8 @@ //@ run-pass trait Foo { + #[allow(improper_ctypes_definitions)] extern "C" fn borrow(&self); + #[allow(improper_ctypes_definitions)] extern "C" fn take(self: Box); } diff --git a/tests/ui/lint/clashing-extern-fn.stderr b/tests/ui/lint/clashing-extern-fn.stderr index 0c27547a6ed8f..e09ac4f71fbc8 100644 --- a/tests/ui/lint/clashing-extern-fn.stderr +++ b/tests/ui/lint/clashing-extern-fn.stderr @@ -6,7 +6,7 @@ LL | fn hidden_niche_transparent_no_niche() -> Option>>`, which is not FFI-safe --> $DIR/clashing-extern-fn.rs:487:46 diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 0c35eb37a4890..bf0a754f81022 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..02dad17490006 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,16 +1,16 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 +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 + | ^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = 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 | -LL | #[deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs new file mode 100644 index 0000000000000..a0cf32237179c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs @@ -0,0 +1,159 @@ +#![deny(improper_ctypes, improper_ctypes_definitions)] + +//@ aux-build: extern_crate_types.rs +//@ compile-flags:--extern extern_crate_types +extern crate extern_crate_types as ext_crate; + +// //////////////////////////////////////////////////////// +// first, the same bank of types as in the extern crate + +// FIXME: maybe re-introduce improper_ctype_definitions (ctype singular) +// as a way to mark ADTs as "let's ignore that they are not actually FFI-unsafe" + +#[repr(C)] +struct SafeStruct (i32); + +#[repr(C)] +struct UnsafeStruct (String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeStruct (String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, +// aliasing, lifetimes, etc...) +// the lint is not raised here, but will be if used in the wrong place +#[repr(C)] +struct UnsafeFromForeignStruct<'a> (&'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeFromForeignStruct<'a> (&'a u32); + + +type SafeFnPtr = extern "C" fn(i32)->i32; + +type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; +//~^ ERROR: `extern` callback uses type `(i32, i32)` + + +// for now, let's not lint on the nonzero assumption, +// because: +// - we don't know if the callback is rust-callee-foreign-caller or the other way around +// - having to cast around function signatures to get function pointers +// would be an awful experience +// so, let's assume that the unsafety in this fnptr +// will be pointed out indirectly by a lint elsewhere +// (note: there's one case where the error would be missed altogether: +// a rust-caller,non-rust-callee callback where the fnptr +// is given as an argument to a rust-callee,non-rust-caller +// FFI boundary) +#[allow(improper_ctypes)] +type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_ctypes)] +type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_ctypes)] +type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////////////////////////// +// then, some functions that use them + +static INT: u32 = 42; + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn1a(e: &String) -> &str {&*e} +extern "C" fn fn1u(e: &String) -> &str {&*e} +//~^ ERROR: `extern` fn uses type `&str` +// | FIXME: not warning about the &String feels wrong, but it's behind a FFI-safe reference so... + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2a(e: UnsafeStruct) {} +extern "C" fn fn2u(e: UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `UnsafeStruct` +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2oa(e: ext_crate::UnsafeStruct) {} +extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::UnsafeStruct` + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3a(e: AllowedUnsafeStruct) {} +extern "C" fn fn3u(e: AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3oa(e: ext_crate::AllowedUnsafeStruct) {} +extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4a(e: UnsafeFromForeignStruct) {} +extern "C" fn fn4u(e: UnsafeFromForeignStruct) {} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4oa(e: ext_crate::UnsafeFromForeignStruct) {} +extern "C" fn fn4ou(e: ext_crate::UnsafeFromForeignStruct) {} +// the block above might become unsafe if/once we lint on the value assumptions of types + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5a() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +extern "C" fn fn5u() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5oa() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} +extern "C" fn fn5ou() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6a() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6u() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6oa() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6ou() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} + +// //////////////////////////////////////////////////////// +// special cases: struct-in-fnptr and fnptr-in-struct + +#[repr(C)] +struct FakeVTable{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + //~^ ERROR: `extern` callback uses type `&[A]` + drop: extern "C" fn(A), + something_else: (A, usize), +} + +type FakeVTableMaker = extern "C" fn() -> FakeVTable; +//~^ ERROR: `extern` callback uses type `FakeVTable` + +#[repr(C)] +#[allow(improper_ctypes)] +struct FakeVTableAllowed{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + drop: extern "C" fn(A), + something_else: (A, usize), +} + +#[allow(improper_ctypes)] +type FakeVTableMakerAllowed = extern "C" fn() -> FakeVTable; + +fn main(){} diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr new file mode 100644 index 0000000000000..84137584a92a3 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr @@ -0,0 +1,104 @@ +error: `extern` callback uses type `(i32, i32)`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:38:34 + | +LL | type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:74:35 + | +LL | extern "C" fn fn1u(e: &String) -> &str {&*e} + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:80:23 + | +LL | extern "C" fn fn2u(e: UnsafeStruct) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`UnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:17:1 + | +LL | struct UnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:84:24 + | +LL | extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::UnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:89:23 + | +LL | extern "C" fn fn3u(e: AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:21:1 + | +LL | struct AllowedUnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:94:24 + | +LL | extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` callback uses type `&[A]`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:138:28 + | +LL | combine: extern "C" fn(&[A]) -> A, + | ^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` callback uses type `FakeVTable`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:144:43 + | +LL | type FakeVTableMaker = extern "C" fn() -> FakeVTable; + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`FakeVTable`) is FFI-unsafe due to a `(u32, usize)` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:136:1 + | +LL | struct FakeVTable{ + | ^^^^^^^^^^^^^^^^^^^^ + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: aborting due to 8 previous errors + diff --git a/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs new file mode 100644 index 0000000000000..3337bd0f588a8 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs @@ -0,0 +1,85 @@ +/// a bank of types (structs, function pointers) that are safe or unsafe for whatever reason, +/// with or without said unsafety being explicitely ignored + +#[repr(C)] +pub struct SafeStruct (pub i32); + +#[repr(C)] +pub struct UnsafeStruct (pub String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeStruct (pub String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, aliasing, +// lifetimes, etc...) +#[repr(C)] +pub struct UnsafeFromForeignStruct<'a> (pub &'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeFromForeignStruct<'a> (pub &'a u32); + + +pub type SafeFnPtr = extern "C" fn(i32)->i32; + +pub type UnsafeFnPtr = extern "C" fn((i32,i32))->i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +pub type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +pub type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////// +/// types used in specific issue-based tests that need extern-crate types + +#[repr(C)] +#[non_exhaustive] +pub struct NonExhaustiveStruct { + pub field: u8 +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveEnum { + variant(u8), +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveCEnum { + variant1, + variant2(()), +} + +#[repr(C)] +pub enum NonExhaustiveEnumVariant { + variant1, + #[non_exhaustive] + variant2((u32,)), +} + +extern "C" { + pub fn nonexhaustivestruct_create() -> *mut NonExhaustiveStruct; + pub fn nonexhaustivestruct_destroy(s: *mut NonExhaustiveStruct); + pub fn nonexhaustiveenum_create() -> *mut NonExhaustiveEnum; + pub fn nonexhaustiveenum_destroy(s: *mut NonExhaustiveEnum); + pub fn nonexhaustivecenum_create() -> *mut NonExhaustiveCEnum; + pub fn nonexhaustivecenum_destroy(s: *mut NonExhaustiveCEnum); + pub fn nonexhaustiveenumvariant_create() -> *mut NonExhaustiveEnumVariant; + pub fn nonexhaustiveenumvariant_destroy(s: *mut NonExhaustiveEnumVariant); + + pub fn nonexhaustivestruct_onstack() -> NonExhaustiveStruct; + pub fn nonexhaustivestruct_owned() -> NonExhaustiveStruct; +} diff --git a/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs new file mode 100644 index 0000000000000..8276329d5dd84 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs @@ -0,0 +1,52 @@ +#![crate_type = "lib"] +#![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 + +pub fn bad_twice(f: Result) {} +//~^ 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` callback uses type `[u8]`, which is not FFI-safe + +enum BadEnum { + A(extern "C" fn([u8])), + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe +} + +enum BadUnion { + A(extern "C" fn([u8])), + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe +} + +type Foo = extern "C" fn([u8]); +//~^ 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` callback uses type `Option<&::FooType>`, which is not FFI-safe + +pub struct FfiUnsafe; + +#[allow(improper_ctypes_definitions)] +extern "C" fn f(_: FfiUnsafe) { + unimplemented!() +} + +pub static BAD: extern "C" fn(FfiUnsafe) = f; +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe + +pub static BAD_TWICE: Result = Ok(f); +//~^ 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` callback uses type `FfiUnsafe`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr new file mode 100644 index 0000000000000..70aefb3be088a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr @@ -0,0 +1,135 @@ +error: `extern` callback uses type `[u8]`, which is not FFI-safe + --> $DIR/ice-fnptr-slicearg.rs:7:29 + | +LL | pub fn bad(f: extern "C" fn([u8])) {} + | ^^^^ not FFI-safe + | + = 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/ice-fnptr-slicearg.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` callback uses type `[u8]`, which is not FFI-safe + --> $DIR/ice-fnptr-slicearg.rs:10:42 + | +LL | pub fn bad_twice(f: Result) {} + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:10:63 + | +LL | pub fn bad_twice(f: Result) {} + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:14:32 + | +LL | struct BadStruct(extern "C" fn([u8])); + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:18:21 + | +LL | A(extern "C" fn([u8])), + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:23:21 + | +LL | A(extern "C" fn([u8])), + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:27:26 + | +LL | type Foo = extern "C" fn([u8]); + | ^^^^ not FFI-safe + | + = 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 + --> $DIR/ice-fnptr-slicearg.rs:34:34 + | +LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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/ice-fnptr-slicearg.rs:44:31 + | +LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; + | ^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout +note: the type is defined here + --> $DIR/ice-fnptr-slicearg.rs:37:1 + | +LL | pub struct FfiUnsafe; + | ^^^^^^^^^^^^^^^^^^^^ + +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe + --> $DIR/ice-fnptr-slicearg.rs:47:44 + | +LL | pub static BAD_TWICE: Result = Ok(f); + | ^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout +note: the type is defined here + --> $DIR/ice-fnptr-slicearg.rs:37:1 + | +LL | pub struct FfiUnsafe; + | ^^^^^^^^^^^^^^^^^^^^ + +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe + --> $DIR/ice-fnptr-slicearg.rs:47:70 + | +LL | pub static BAD_TWICE: Result = Ok(f); + | ^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout +note: the type is defined here + --> $DIR/ice-fnptr-slicearg.rs:37:1 + | +LL | pub struct FfiUnsafe; + | ^^^^^^^^^^^^^^^^^^^^ + +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe + --> $DIR/ice-fnptr-slicearg.rs:51:36 + | +LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; + | ^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout +note: the type is defined here + --> $DIR/ice-fnptr-slicearg.rs:37:1 + | +LL | pub struct FfiUnsafe; + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + 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 56% rename from tests/ui/lint/improper-ctypes/lint-73249-2.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs index 31af0e3d381ef..006c8b27306b6 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs @@ -1,6 +1,11 @@ +//@ check-pass // possible FIXME: see below + #![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 () {} @@ -24,7 +29,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-3.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs similarity index 60% rename from tests/ui/lint/improper-ctypes/lint-73249-3.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs index 8bdf536bf77e6..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 {} @@ -18,7 +21,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/ice-fully-normalize-3.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr new file mode 100644 index 0000000000000..b19975ec9dad1 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr @@ -0,0 +1,21 @@ +error: `extern` block uses type `A`, which is not FFI-safe + --> $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/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/ice-fully-normalize-3.rs:2:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + 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 63% rename from tests/ui/lint/improper-ctypes/lint-73249-5.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs index cc6da59950d7a..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 {} @@ -18,7 +21,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/ice-fully-normalize-5.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr new file mode 100644 index 0000000000000..c8513322fbbc8 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr @@ -0,0 +1,21 @@ +error: `extern` block uses type `A`, which is not FFI-safe + --> $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/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/ice-fully-normalize-5.rs:2:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + 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/ice-irregular-recursive-types.rs b/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs new file mode 100644 index 0000000000000..22975b45a3b4d --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs @@ -0,0 +1,21 @@ +//@ check-pass + +//! this test checks that irregular recursive types do not cause stack overflow in ImproperCTypes +//! Issue: https://github.com/rust-lang/rust/issues/94223 + +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::marker::PhantomData; + +#[repr(C)] +struct A { + a: *const A>, // without a recursion limit, checking this ends up creating checks for + // infinitely deep types the likes of `A>>>>>` + p: PhantomData, +} + +extern "C" { + fn f(a: *const A<()>); +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/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 66% rename from tests/ui/lint/improper-ctypes/mustpass-134060.stderr rename to tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr index 791b2f7370983..a9c776eeef88c 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr +++ b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr @@ -1,12 +1,12 @@ 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 | = 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/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 53% 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 1ca59c6868d6d..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, @@ -20,8 +23,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/issue-fnptr-wrapped-unit-1.stderr b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr new file mode 100644 index 0000000000000..0849882eade56 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr @@ -0,0 +1,47 @@ +error: `extern` fn uses type `Bar`, which is not FFI-safe + --> $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/issue-fnptr-wrapped-unit-1.rs:19:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` + = note: `NotSafe` has unspecified layout +note: the type is defined here + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 + | +LL | struct NotSafe(u32); + | ^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $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/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/issue-fnptr-wrapped-unit-1.rs:19:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` + = note: `NotSafe` has unspecified layout +note: the type is defined here + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 + | +LL | struct NotSafe(u32); + | ^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs similarity index 83% rename from tests/ui/lint/improper-ctypes/mustpass-113436.rs rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs index d5acdc45f92e5..1cf4cf18d0269 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113436.rs +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs @@ -1,6 +1,9 @@ //@ check-pass #![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-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr deleted file mode 100644 index f01dc3b6e0d1e..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error: `extern` fn uses type `NotSafe`, which is not FFI-safe - --> $DIR/lint-113436-1.rs:22:22 - | -LL | extern "C" fn bar(x: Bar) -> Bar { - | ^^^ 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-113436-1.rs:13:1 - | -LL | struct NotSafe(u32); - | ^^^^^^^^^^^^^^ -note: the lint level is defined here - --> $DIR/lint-113436-1.rs:1:9 - | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: `extern` fn uses type `NotSafe`, which is not FFI-safe - --> $DIR/lint-113436-1.rs:22:30 - | -LL | extern "C" fn bar(x: Bar) -> Bar { - | ^^^ 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-113436-1.rs:13:1 - | -LL | struct NotSafe(u32); - | ^^^^^^^^^^^^^^ - -error: aborting due to 2 previous errors - 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-73249-3.stderr b/tests/ui/lint/improper-ctypes/lint-73249-3.stderr deleted file mode 100644 index ebc9eb5eb8274..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73249-3.rs:21:25 - | -LL | pub fn lint_me() -> A; - | ^ not FFI-safe - | - = note: opaque types have no C equivalent -note: the lint level is defined here - --> $DIR/lint-73249-3.rs:2:9 - | -LL | #![deny(improper_ctypes)] - | ^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error - diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr deleted file mode 100644 index 484927f57fead..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73249-5.rs:21:25 - | -LL | pub fn lint_me() -> A; - | ^ not FFI-safe - | - = note: opaque types have no C equivalent -note: the lint level is defined here - --> $DIR/lint-73249-5.rs:2:9 - | -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 deleted file mode 100644 index ac24f61b0ac7a..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![crate_type = "lib"] -#![deny(improper_ctypes_definitions)] - -pub fn bad(f: extern "C" fn([u8])) {} -//~^ ERROR `extern` fn 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 - -struct BadStruct(extern "C" fn([u8])); -//~^ ERROR `extern` fn 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 -} - -enum BadUnion { - A(extern "C" fn([u8])), - //~^ ERROR `extern` fn 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 - -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 - -pub struct FfiUnsafe; - -#[allow(improper_ctypes_definitions)] -extern "C" fn f(_: FfiUnsafe) { - unimplemented!() -} - -pub static BAD: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe - -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 - -pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn 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 deleted file mode 100644 index 008debf8f010a..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ /dev/null @@ -1,135 +0,0 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:4:15 - | -LL | pub fn bad(f: extern "C" fn([u8])) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = 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 - | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:28 - | -LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:49 - | -LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:11:18 - | -LL | struct BadStruct(extern "C" fn([u8])); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:15:7 - | -LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:20:7 - | -LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` fn uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:24:12 - | -LL | type Foo = extern "C" fn([u8]); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe - | - = 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 - --> $DIR/lint-94223.rs:31:20 - | -LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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` fn uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:41:17 - | -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 -note: the type is defined here - --> $DIR/lint-94223.rs:34:1 - | -LL | pub struct FfiUnsafe; - | ^^^^^^^^^^^^^^^^^^^^ - -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:30 - | -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 -note: the type is defined here - --> $DIR/lint-94223.rs:34:1 - | -LL | pub struct FfiUnsafe; - | ^^^^^^^^^^^^^^^^^^^^ - -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:56 - | -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 -note: the type is defined here - --> $DIR/lint-94223.rs:34:1 - | -LL | pub struct FfiUnsafe; - | ^^^^^^^^^^^^^^^^^^^^ - -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:48:22 - | -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 -note: the type is defined here - --> $DIR/lint-94223.rs:34:1 - | -LL | pub struct FfiUnsafe; - | ^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 12 previous errors - 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 diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 7dc06079fa32c..108fd83577335 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,12 +1,18 @@ #![feature(rustc_private)] +#![feature(extern_types)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] #![allow(private_interfaces)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] 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 { } trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } @@ -20,7 +26,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 +34,7 @@ pub struct TransparentI128(i128); #[repr(transparent)] pub struct TransparentStr(&'static str); #[repr(transparent)] -pub struct TransparentBadFn(RustBadRet); +pub struct TransparentBoxFn(RustBoxRet); #[repr(transparent)] pub struct TransparentInt(u32); #[repr(transparent)] @@ -39,21 +45,39 @@ pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>); pub struct TransparentUnit(f32, PhantomData); #[repr(transparent)] pub struct TransparentCustomZst(i32, ZeroSize); +#[repr(C)] +pub struct UnsizedStructBecauseForeign { + sized: u32, + unszd: UnsizedOpaque, +} +#[repr(C)] +pub struct UnsizedStructBecauseDyn { + sized: u32, + unszd: dyn Debug, +} + +#[repr(C)] +pub struct TwoBadTypes<'a> { + non_c_type: char, + ref_with_mdata: &'a [u8], +} #[repr(C)] pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); extern "C" { - pub fn ptr_type1(size: *const Foo); //~ ERROR: uses type `Foo` - pub fn ptr_type2(size: *const Foo); //~ ERROR: uses type `Foo` + pub fn ptr_type1(size: *const Foo); + pub fn ptr_type2(size: *const Foo); pub fn ptr_unit(p: *const ()); - pub fn ptr_tuple(p: *const ((),)); //~ ERROR: uses type `((),)` - pub fn slice_type(p: &[u32]); //~ ERROR: uses type `[u32]` - pub fn str_type(p: &str); //~ ERROR: uses type `str` - pub fn box_type(p: Box); //~ ERROR uses type `Box` + pub fn ptr_tuple(p: *const ((),)); + pub fn slice_type(p: &[u32]); //~ ERROR: uses type `&[u32]` + pub fn str_type(p: &str); //~ ERROR: uses type `&str` + pub fn box_type(p: Box); pub fn opt_box_type(p: Option>); pub fn char_type(p: char); //~ ERROR uses type `char` - pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `dyn Bar` + pub fn 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)` pub fn zero_size(p: ZeroSize); //~ ERROR uses type `ZeroSize` @@ -63,10 +87,20 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); //~ ERROR: uses type `Box` - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: uses type `Box` + pub fn fn_contained(p: RustBoxRet); + pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `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<'_>` + //~| 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` pub fn no_niche_a(a: Option>); //~^ ERROR: uses type `Option>` @@ -104,6 +138,15 @@ extern "C" { pub fn good19(_: &String); } +static DEFAULT_U32: u32 = 42; +#[no_mangle] +static EXPORTED_STATIC: &u32 = &DEFAULT_U32; +#[no_mangle] +static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; +//~^ ERROR: uses type `&str` +#[export_name="EXPORTED_STATIC_MUT_BUT_RENAMED"] +static mut EXPORTED_STATIC_MUT: &u32 = &DEFAULT_U32; + #[cfg(not(target_arch = "wasm32"))] extern "C" { pub fn good1(size: *const c_int); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 6f8b951c53d70..ce4d324a71a2e 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -1,74 +1,28 @@ -error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:47:28 - | -LL | pub fn ptr_type1(size: *const Foo); - | ^^^^^^^^^^ not FFI-safe +error: `extern` block uses type `&[u32]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:73:26 | - = 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 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:4:9 + --> $DIR/lint-ctypes.rs:7:9 | -LL | #![deny(improper_ctypes)] +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:48:28 - | -LL | pub fn ptr_type2(size: *const Foo); - | ^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout -note: the type is defined here - --> $DIR/lint-ctypes.rs:25:1 - | -LL | pub struct Foo; - | ^^^^^^^^^^^^^^ - -error: `extern` block uses type `((),)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:50:25 - | -LL | pub fn ptr_tuple(p: *const ((),)); - | ^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a struct instead - = note: tuples have unspecified layout - -error: `extern` block uses type `[u32]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:51:26 - | -LL | pub fn slice_type(p: &[u32]); - | ^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:52:24 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:74:24 | LL | pub fn str_type(p: &str); | ^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:53:24 - | -LL | pub fn box_type(p: Box); - | ^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:55:25 + --> $DIR/lint-ctypes.rs:77:25 | LL | pub fn char_type(p: char); | ^^^^ not FFI-safe @@ -76,16 +30,25 @@ LL | pub fn char_type(p: char); = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent -error: `extern` block uses type `dyn Bar`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:56:26 +error: `extern` block uses type `Option<(u32) is 0..>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:78:27 + | +LL | pub fn pat_type1() -> Option; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum + = note: enum has no representation hint + +error: `extern` block uses type `&dyn Bar`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:80:26 | LL | pub fn trait_type(p: &dyn Bar); | ^^^^^^^^ not FFI-safe | - = note: trait objects have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:57:26 + --> $DIR/lint-ctypes.rs:81:26 | LL | pub fn tuple_type(p: (i32, i32)); | ^^^^^^^^^^ not FFI-safe @@ -94,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:58:27 + --> $DIR/lint-ctypes.rs:82:27 | LL | pub fn tuple_type2(p: I32Pair); | ^^^^^^^ not FFI-safe @@ -103,34 +66,34 @@ LL | pub fn tuple_type2(p: I32Pair); = note: tuples have unspecified layout error: `extern` block uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:59:25 + --> $DIR/lint-ctypes.rs:83:25 | 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:21: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:60:33 + --> $DIR/lint-ctypes.rs:84:33 | LL | pub fn zero_size_phantom(p: ZeroSizeWithPhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: composed only of `PhantomData` note: the type is defined here - --> $DIR/lint-ctypes.rs:44:1 + --> $DIR/lint-ctypes.rs:66:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:63:12 + --> $DIR/lint-ctypes.rs:87:12 | LL | -> ::std::marker::PhantomData; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -138,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:64:23 + --> $DIR/lint-ctypes.rs:88:23 | LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe @@ -147,7 +110,7 @@ LL | pub fn fn_type(p: RustFn); = note: this function pointer has Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:65:24 + --> $DIR/lint-ctypes.rs:89:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -155,35 +118,23 @@ LL | pub fn fn_type2(p: fn()); = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:66:28 - | -LL | pub fn fn_contained(p: RustBadRet); - | ^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout - -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:67:31 +error: `extern` block uses type `TransparentStr`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:91:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:68:30 - | -LL | pub fn transparent_fn(p: TransparentBadFn); - | ^^^^^^^^^^^^^^^^ not FFI-safe + = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:35:1 | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout +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 error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:69:27 + --> $DIR/lint-ctypes.rs:93:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -191,8 +142,63 @@ 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:95: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:95: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:95:59 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `char` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:60:1 + | +LL | pub struct TwoBadTypes<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:95:59 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:60:1 + | +LL | pub struct TwoBadTypes<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:103:47 + | +LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:26 + --> $DIR/lint-ctypes.rs:105:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -201,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:73:26 + --> $DIR/lint-ctypes.rs:107:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -209,5 +215,19 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: foreign-code-reachable static uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:145:29 + | +LL | static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-ctypes.rs:7:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 22 previous errors diff --git a/tests/ui/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-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 0b84098e39067..731d97f2bcd52 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, improper_ctypes_definitions)] use std::default::Default; use std::marker::PhantomData; @@ -26,7 +26,7 @@ pub struct ZeroSize; pub type RustFn = fn(); -pub type RustBadRet = extern "C" fn() -> Box; +pub type RustBadRet = extern "C" fn() -> (u32,u64); //~ ERROR uses type `(u32, u64)` pub type CVoidRet = (); @@ -68,14 +68,15 @@ pub extern "C" fn ptr_unit(p: *const ()) { } pub extern "C" fn ptr_tuple(p: *const ((),)) { } pub extern "C" fn slice_type(p: &[u32]) { } -//~^ ERROR: uses type `[u32]` +//~^ ERROR: uses type `&[u32]` pub extern "C" fn str_type(p: &str) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn box_type(p: Box) { } pub extern "C" fn opt_box_type(p: Option>) { } +// no error here! pub extern "C" fn boxed_slice(p: Box<[u8]>) { } //~^ ERROR: uses type `Box<[u8]>` @@ -115,17 +116,17 @@ 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) { } pub extern "C" fn good3(fptr: Option) { } -pub extern "C" fn good4(aptr: &[u8; 4 as usize]) { } +pub extern "C" fn argument_with_assumptions_4(aptr: &[u8; 4 as usize]) { } pub extern "C" fn good5(s: StructWithProjection) { } -pub extern "C" fn good6(s: StructWithProjectionAndLifetime) { } +pub extern "C" fn argument_with_assumptions_6(s: StructWithProjectionAndLifetime) { } pub extern "C" fn good7(fptr: extern "C" fn() -> ()) { } @@ -141,7 +142,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 34e3bd021b92d..1658a5c23e08d 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -1,52 +1,68 @@ -error: `extern` fn uses type `[u32]`, which is not FFI-safe +error: `extern` callback uses type `(u32, u64)`, which is not FFI-safe + --> $DIR/lint-fn.rs:29:42 + | +LL | pub type RustBadRet = extern "C" fn() -> (u32,u64); + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/lint-fn.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&[u32]`, which is not FFI-safe --> $DIR/lint-fn.rs:70:33 | LL | pub extern "C" fn slice_type(p: &[u32]) { } | ^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = 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:9 + --> $DIR/lint-fn.rs:2:26 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:73:31 | LL | pub extern "C" fn str_type(p: &str) { } | ^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe - --> $DIR/lint-fn.rs:80:34 + --> $DIR/lint-fn.rs:81:34 | LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = help: consider using a raw pointer 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 - --> $DIR/lint-fn.rs:83:35 + --> $DIR/lint-fn.rs:84:35 | LL | pub extern "C" fn boxed_string(p: Box) { } | ^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = help: consider using `*const u8` and a length instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box`, which is not FFI-safe - --> $DIR/lint-fn.rs:86:34 + --> $DIR/lint-fn.rs:87:34 | LL | pub extern "C" fn boxed_trait(p: Box) { } | ^^^^^^^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `char`, which is not FFI-safe - --> $DIR/lint-fn.rs:89:32 + --> $DIR/lint-fn.rs:90:32 | LL | pub extern "C" fn char_type(p: char) { } | ^^^^ not FFI-safe @@ -55,7 +71,7 @@ LL | pub extern "C" fn char_type(p: char) { } = note: the `char` type has no C equivalent error: `extern` fn uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-fn.rs:92:33 + --> $DIR/lint-fn.rs:93:33 | LL | pub extern "C" fn tuple_type(p: (i32, i32)) { } | ^^^^^^^^^^ not FFI-safe @@ -64,7 +80,7 @@ LL | pub extern "C" fn tuple_type(p: (i32, i32)) { } = note: tuples have unspecified layout error: `extern` fn uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-fn.rs:95:34 + --> $DIR/lint-fn.rs:96:34 | LL | pub extern "C" fn tuple_type2(p: I32Pair) { } | ^^^^^^^ not FFI-safe @@ -73,13 +89,13 @@ LL | pub extern "C" fn tuple_type2(p: I32Pair) { } = note: tuples have unspecified layout error: `extern` fn uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-fn.rs:98:32 + --> $DIR/lint-fn.rs:99:32 | LL | pub extern "C" fn zero_size(p: ZeroSize) { } | ^^^^^^^^ not FFI-safe | = 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 | @@ -87,7 +103,7 @@ LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:101:40 + --> $DIR/lint-fn.rs:102:40 | LL | pub extern "C" fn zero_size_phantom(p: ZeroSizeWithPhantomData) { } | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -100,7 +116,7 @@ LL | pub struct ZeroSizeWithPhantomData(PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:104:51 + --> $DIR/lint-fn.rs:105:51 | LL | pub extern "C" fn zero_size_phantom_toplevel() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -108,7 +124,7 @@ LL | pub extern "C" fn zero_size_phantom_toplevel() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `fn()`, which is not FFI-safe - --> $DIR/lint-fn.rs:109:30 + --> $DIR/lint-fn.rs:110:30 | LL | pub extern "C" fn fn_type(p: RustFn) { } | ^^^^^^ not FFI-safe @@ -117,7 +133,7 @@ LL | pub extern "C" fn fn_type(p: RustFn) { } = note: this function pointer has Rust-specific calling convention error: `extern` fn uses type `fn()`, which is not FFI-safe - --> $DIR/lint-fn.rs:112:31 + --> $DIR/lint-fn.rs:113:31 | LL | pub extern "C" fn fn_type2(p: fn()) { } | ^^^^ not FFI-safe @@ -125,17 +141,23 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/lint-fn.rs:117:38 +error: `extern` fn uses type `TransparentStr`, which is not FFI-safe + --> $DIR/lint-fn.rs:118:38 | 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: 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:169:43 + --> $DIR/lint-fn.rs:170:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,22 +165,22 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:182:39 + --> $DIR/lint-fn.rs:183:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Vec` + = note: `Vec` has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:185:41 + --> $DIR/lint-fn.rs:186:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Vec` + = note: `Vec` has unspecified layout -error: aborting due to 17 previous errors +error: aborting due to 18 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs index 61e95dc5a464c..de731171b9a66 100644 --- a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs @@ -1,8 +1,10 @@ -//@ check-pass +//! This test checks that the depth limit of the ImproperCTypes lints counts the depth +//! of a type properly. +//! Issue: https://github.com/rust-lang/rust/issues/130757 #![recursion_limit = "5"] #![allow(unused)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes_definitions)] #[repr(C)] struct F1(*const ()); @@ -15,7 +17,7 @@ struct F4(*const ()); #[repr(C)] struct F5(*const ()); #[repr(C)] -struct F6(*const ()); +struct F6([char;8]); //oops! #[repr(C)] struct B { @@ -24,9 +26,10 @@ struct B { f3: F3, f4: F4, f5: F5, - f6: F6, + f6: F6, // when the recursion limit hits, things are assumed safe, so this should error } extern "C" fn foo(_: B) {} +//~^ ERROR: uses type `char` fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr new file mode 100644 index 0000000000000..ce565c542c94a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr @@ -0,0 +1,16 @@ +error: `extern` fn uses type `char`, which is not FFI-safe + --> $DIR/lint-non-recursion-limit.rs:32:22 + | +LL | extern "C" fn foo(_: B) {} + | ^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent +note: the lint level is defined here + --> $DIR/lint-non-recursion-limit.rs:7:9 + | +LL | #![deny(improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + 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-transparent-help.rs b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs new file mode 100644 index 0000000000000..578e45359448a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs @@ -0,0 +1,21 @@ +#![deny(improper_ctypes_definitions)] +use std::marker::PhantomData; +use std::collections::HashMap; +use std::ffi::c_void; + +// [option 1] oops, we forgot repr(C) +struct DictPhantom<'a, A,B:'a>{ + value_info: PhantomData<&'a B>, + full_dict_info: PhantomData>, +} + +#[repr(C)] // [option 2] oops, we meant repr(transparent) +struct MyTypedRawPointer<'a,T:'a>{ + ptr: *const c_void, + metadata: DictPhantom<'a,T,T>, +} + +extern "C" fn example_use(_e: MyTypedRawPointer) {} +//~^ ERROR: uses type `MyTypedRawPointer<'_, i32>` + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr b/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr new file mode 100644 index 0000000000000..6528679675a98 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr @@ -0,0 +1,28 @@ +error: `extern` fn uses type `MyTypedRawPointer<'_, i32>`, which is not FFI-safe + --> $DIR/lint-transparent-help.rs:18:31 + | +LL | extern "C" fn example_use(_e: MyTypedRawPointer) {} + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `MyTypedRawPointer<'_, i32>` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`MyTypedRawPointer<'_, i32>`) is FFI-unsafe due to a `DictPhantom<'_, i32, i32>` field +note: the type is defined here + --> $DIR/lint-transparent-help.rs:13:1 + | +LL | struct MyTypedRawPointer<'a,T:'a>{ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `DictPhantom<'_, i32, i32>` + = note: `DictPhantom<'_, i32, i32>` has unspecified layout +note: the type is defined here + --> $DIR/lint-transparent-help.rs:7:1 + | +LL | struct DictPhantom<'a, A,B:'a>{ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint-transparent-help.rs:1:9 + | +LL | #![deny(improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs new file mode 100644 index 0000000000000..af460e218179c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs @@ -0,0 +1,311 @@ +// Trying to cover as many ty_kinds as possible in the code for ImproperCTypes lint +//@ edition:2018 + +#![allow(dead_code,unused_variables)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +// we want ALL the ty_kinds, including the feature-gated ones +#![feature(extern_types)] +#![feature(never_type)] +#![feature(inherent_associated_types)] //~ WARN: is incomplete +#![feature(async_trait_bounds)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] + +// ty_kinds not found so far: +// Placeholder, Bound, Infer, Error, +// Alias +// FnDef, Closure, Coroutine, ClosureCoroutine, CoroutineWitness, + +use std::ptr::from_ref; +use std::ptr::NonNull; +use std::mem::{MaybeUninit, size_of}; +use std::num::NonZero; +use std::pat::pattern_type; + +#[repr(C)] +struct SomeStruct{ + a: u8, + b: i32, +} +impl SomeStruct{ + extern "C" fn klol( + // Ref[Struct] + &self + ){} +} + +#[repr(C)] +#[derive(Clone,Copy)] +struct TemplateStruct where T: std::ops::Add+Copy { + one: T, + two: T, +} +impl TemplateStruct { + type Out = ::Output; +} + +extern "C" fn tstruct_sum( + // Ref[Struct] + slf: Option<&TemplateStruct> + // Option> ...not Inherent. dangit +) -> Option::Out>> { + Some(Box::new(slf?.one + slf?.two)) +} + +#[repr(C)] +union SomeUnion{ + sz: u8, + us: i8, +} +#[repr(C)] +enum SomeEnum{ + Everything=42, + NotAU8=256, + SomePrimeNumber=23, +} + +pub trait TimesTwo: std::ops::Add + Sized + Clone + where for<'a> &'a Self: std::ops::Add<&'a Self>, + *const Self: std::ops::Add<*const Self>, + Box: std::ops::Add>, +{ + extern "C" fn t2_own( + // Param + self + // Alias + ) -> >::Output { + self.clone() + self + } + // it ICEs (https://github.com/rust-lang/rust/issues/134587) :( + //extern "C" fn t2_ptr( + // // Ref[Param] + // slf: *const Self + // // Alias + //) -> <*const Self as std::ops::Add<*const Self>>::Output { + // slf + slf + //} + extern "C" fn t2_box( + // Box[Param] + self: Box, + // Alias + ) -> as std::ops::Add>>::Output { + self.clone() + self + } + extern "C" fn t2_ref( + // Ref[Param] + &self + // Alias + ) -> <&Self as std::ops::Add<&Self>>::Output { + self + self + } +} + +extern "C" {type ExtType;} + +#[repr(C)] +pub struct StructWithDyn(dyn std::fmt::Debug); + +extern "C" { + // variadic args aren't listed as args in a way that allows type checking. + // this is fine (TM) + fn variadic_function(e: ...); +} + +extern "C" fn all_ty_kinds<'a,const N:usize,T>( + // UInt, Int, Float, Bool + u:u8, i:i8, f:f64, b:bool, + // Struct + s:String, //~ ERROR: uses type `String` + // Ref[Str] + s2:&str, //~ ERROR: uses type `&str` + // Char + c: char, //~ ERROR: uses type `char` + // Ref[Slice] + s3:&[u8], //~ ERROR: uses type `&[u8]` + // Array (this gets caught outside of the code we want to test) + s4:[u8;N], //~ ERROR: uses type `[u8; N]` + // Tuple + p:(u8, u8), //~ ERROR: uses type `(u8, u8)` + // also Tuple + (p2, p3):(u8, u8), //~ ERROR: uses type `(u8, u8)` + // Pat + nz: pattern_type!(u32 is 1..), + // Struct + SomeStruct{b:p4,..}: SomeStruct, + // Union + u2: SomeUnion, + // Enum, + e: SomeEnum, + // Param + d: impl Clone, + // Param + t: T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Never + x:!, //~ ERROR: uses type `!` + //r1: &u8, r2: *const u8, r3: Box, + // FnPtr + f2: fn(u8)->u8, //~ ERROR: uses type `fn(u8) -> u8` + // Ref[Dynamic] + f3: &'a dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // Param, + a: impl async Fn(u8)->u8, //FIXME: eventually, be able to peer into type params + // Alias +) -> impl std::fmt::Debug { //~ ERROR: uses type `impl Debug` + 3_usize +} + +extern "C" fn all_ty_kinds_in_ptr( + // Ptr[UInt], Ptr[Int], Ptr[Float], Ptr[Bool] + u: *const u8, i: *const i8, f: *const f64, b: *const bool, + // Ptr[Struct] + s: *const String, + // Ptr[Str] + s2: *const str, //~ ERROR: uses type `*const str` + // Ptr[Char] + c: *const char, + // Ptr[Slice] + s3: *const [u8], //~ ERROR: uses type `*const [u8]` + // Ptr[Array] (this gets caught outside of the code we want to test) + s4: *const [u8;N], + // Ptr[Tuple] + p: *const (u8,u8), + // Tuple + (p2, p3):(*const u8, *const u8), //~ ERROR: uses type `(*const u8, *const u8)` + // Pat + nz: *const pattern_type!(u32 is 1..), + // Ptr[Struct] + SomeStruct{b: ref p4,..}: & SomeStruct, + // Ptr[Union] + u2: *const SomeUnion, + // Ptr[Enum], + e: *const SomeEnum, + // Param + d: *const impl Clone, + // Param + t: *const T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ptr[Struct] + e3: *const StructWithDyn, //~ ERROR: uses type `*const StructWithDyn` + // Ptr[Never] + x: *const !, + //r1: &u8, r2: *const u8, r3: Box, + // Ptr[FnPtr] + f2: *const fn(u8)->u8, + // Ptr[Dynamic] + f3: *const dyn Fn(u8)->u8, //~ ERROR: uses type `*const dyn Fn(u8) -> u8` + // Ptr[Dynamic] + d2: *const dyn std::cmp::PartialOrd, //~ ERROR: uses type `*const dyn PartialOrd` + // Ptr[Param], + a: *const impl async Fn(u8)->u8, + // Alias +) -> *const dyn std::fmt::Debug { //~ ERROR: uses type `*const dyn Debug` + todo!() +} + +extern "C" { +fn all_ty_kinds_in_ref<'a>( + // Ref[UInt], Ref[Int], Ref[Float], Ref[Bool] + u: &u8, i: &'a i8, f: &f64, b: &bool, + // Ref[Struct] + s: &String, + // Ref[Str] + s2: &str, //~ ERROR: uses type `&str` + // Ref[Char] + c: &char, + // Ref[Slice] + s3: &[u8], //~ ERROR: uses type `&[u8]` + // deactivated here, because this is a function *declaration* (param N unacceptable) + // s4: &[u8;N], + // Ref[Tuple] + p: &(u8, u8), + // deactivated here, because this is a function *declaration* (patterns unacceptable) + // (p2, p3):(&u8, &u8), + // Pat + nz: &pattern_type!(u32 is 1..), + // deactivated here, because this is a function *declaration* (pattern unacceptable) + // SomeStruct{b: ref p4,..}: &SomeStruct, + // Ref[Union] + u2: &SomeUnion, + // Ref[Enum], + e: &SomeEnum, + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // d: &impl Clone, + // deactivated here, because this is a function *declaration* (type param unacceptable) + // t: &T, + // Ref[Foreign] + e2: &ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Ref[Never] + x: &!, + //r1: &u8, r2: &u8, r3: Box, + // Ref[FnPtr] + f2: &fn(u8)->u8, + // Ref[Dynamic] + f3: &dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // a: &impl async Fn(u8)->u8, + // Ref[Dynamic] +) -> &'a dyn std::fmt::Debug; //~ ERROR: uses type `&dyn Debug` +} + +extern "C" fn all_ty_kinds_in_box( + // Box[UInt], Box[Int], Box[Float], Box[Bool] + u: Option>, i: Option>, f: Option>, b: Option>, + // Box[Struct] + s: Option>, + // Box[Str] + s2: Box, //~ ERROR: uses type `Box` + // Box[Char] + c: Box, + // Box[Slice] + s3: Box<[u8]>, //~ ERROR: uses type `Box<[u8]>` + // Box[Array] (this gets caught outside of the code we want to test) + s4: Option>, + // Box[Tuple] + p: Option>, + // also Tuple + (p2,p3):(Box, Box), //~ ERROR: uses type `(Box, Box)` + // Pat + nz: Option>, + // Ref[Struct] + SomeStruct{b: ref p4,..}: &SomeStruct, + // Box[Union] + u2: Option>, + // Box[Enum], + e: Option>, + // Box[Param] + d: Option>, + // Box[Param] + t: Option>, + // deactivated, we can't deallocate an external type in rust + //e2: Option>, + // Box[Struct] + e3: Box, //~ ERROR: uses type `Box` + // Box[Never] + x: Box, + //r1: Box, + // Box[FnPtr] + f2: Boxu8>, + // Box[Dynamic] + f3: Boxu8>, //~ ERROR: uses type `Box u8>` + // Box[Dynamic] + d2: Box>, //~ ERROR: uses type `Box>` + // Option[Box[Param]], + a: Optionu8>>, + // Box[Dynamic] +) -> Box { //~ ERROR: uses type `Box` + u.unwrap() +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr new file mode 100644 index 0000000000000..fa29541618e41 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr @@ -0,0 +1,301 @@ +warning: the feature `inherent_associated_types` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/lint-tykind-fuzz.rs:10:12 + | +LL | #![feature(inherent_associated_types)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #8995 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: `extern` fn uses type `String`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:119:7 + | +LL | s:String, + | ^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:121:8 + | +LL | s2:&str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `char`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:123:8 + | +LL | c: char, + | ^^^^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` fn uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:125:8 + | +LL | s3:&[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `[u8; N]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:127:8 + | +LL | s4:[u8;N], + | ^^^^^^ not FFI-safe + | + = help: consider passing a pointer to the array + = note: passing raw arrays by value is not FFI-safe + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:129:7 + | +LL | p:(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:131:14 + | +LL | (p2, p3):(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:147:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `!`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:149:7 + | +LL | x:!, + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:152:9 + | +LL | f2: fn(u8)->u8, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using an `extern fn(...) -> ...` function pointer instead + = note: this function pointer has Rust-specific calling convention + +error: `extern` fn uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:154:9 + | +LL | f3: &'a dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:156:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `impl Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:160:6 + | +LL | ) -> impl std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: opaque types have no C equivalent + +error: `extern` fn uses type `*const str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:170:9 + | +LL | s2: *const str, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const [u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:174:9 + | +LL | s3: *const [u8], + | ^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(*const u8, *const u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:180:14 + | +LL | (p2, p3):(*const u8, *const u8), + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `*const StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:196:9 + | +LL | e3: *const StructWithDyn, + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:203:9 + | +LL | f3: *const dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:205:9 + | +LL | d2: *const dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:209:6 + | +LL | ) -> *const dyn std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:220:9 + | +LL | s2: &str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:224:9 + | +LL | s3: &[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:246:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:253:9 + | +LL | f3: &dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:255:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:259:6 + | +LL | ) -> &'a dyn std::fmt::Debug; + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:268:9 + | +LL | s2: Box, + | ^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:272:9 + | +LL | s3: Box<[u8]>, + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(Box, Box)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:278:13 + | +LL | (p2,p3):(Box, Box), + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:294:9 + | +LL | e3: Box, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box u8>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:301:9 + | +LL | f3: Boxu8>, + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:303:9 + | +LL | d2: Box>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:307:6 + | +LL | ) -> Box { + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: aborting due to 33 previous errors; 1 warning emitted + diff --git a/tests/ui/lint/improper-ctypes/lint-uninhabited.rs b/tests/ui/lint/improper-ctypes/lint-uninhabited.rs 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..852b8115a5d46 --- /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/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..5c4df4f02cdce 100644 --- a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr +++ b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr @@ -4,8 +4,8 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn foo(x: A); | ^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | @@ -17,18 +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 | - = 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 `B` + = note: `B` has unspecified layout note: the type is defined here - --> $DIR/repr-rust-is-undefined.rs:8:1 + --> $DIR/repr-rust-is-undefined.rs:13:1 | -LL | struct A { +LL | struct B { | ^^^^^^^^ error: `extern` block uses type `A`, which is not FFI-safe @@ -37,36 +37,42 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn qux(x: A2); | ^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | 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 | - = 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 `B` + = note: `B` has unspecified layout note: the type is defined here - --> $DIR/repr-rust-is-undefined.rs:8:1 + --> $DIR/repr-rust-is-undefined.rs:13:1 | -LL | struct A { +LL | struct B { | ^^^^^^^^ -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 | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = 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)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | diff --git a/tests/ui/repr/repr-transparent-issue-87496.stderr b/tests/ui/repr/repr-transparent-issue-87496.stderr index aee31212b4ed2..352ac61c4f696 100644 --- a/tests/ui/repr/repr-transparent-issue-87496.stderr +++ b/tests/ui/repr/repr-transparent-issue-87496.stderr @@ -4,13 +4,13 @@ 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 | 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 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/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]; diff --git a/tests/ui/structs-enums/foreign-struct.rs b/tests/ui/structs-enums/foreign-struct.rs index f339c191ae806..763996de63643 100644 --- a/tests/ui/structs-enums/foreign-struct.rs +++ b/tests/ui/structs-enums/foreign-struct.rs @@ -1,17 +1,22 @@ //@ run-pass #![allow(dead_code)] -#![allow(non_camel_case_types)] // Passing enums by value - -pub enum void {} +#[repr(C)] +pub enum PoorQualityAnyEnum { + None = 0, + Int = 1, + Long = 2, + Float = 17, + Double = 18, +} mod bindgen { - use super::void; + use super::PoorQualityAnyEnum; extern "C" { - pub fn printf(v: void); + pub fn printf(v: PoorQualityAnyEnum); } } diff --git a/tests/ui/union/union-repr-c.stderr b/tests/ui/union/union-repr-c.stderr index 0beb7c376f3ad..dc8335da09b28 100644 --- a/tests/ui/union/union-repr-c.stderr +++ b/tests/ui/union/union-repr-c.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `W`, which is not FFI-safe LL | static FOREIGN2: W; | ^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union + = help: consider adding a `#[repr(C)]` attribute to this union = note: this union has unspecified layout note: the type is defined here --> $DIR/union-repr-c.rs:9:1