From 57c7507737453333e58155d076d7e441547c07b0 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 11 Dec 2024 23:54:33 +0100 Subject: [PATCH 1/9] Changes to the ImproperCTypes lint, start of take 1 [does not pass tests] This reverts commit 1fcbb1e338f50e752a64c561838aaa7eb111e039. --- compiler/rustc_lint/messages.ftl | 13 +- compiler/rustc_lint/src/lints.rs | 45 ++- compiler/rustc_lint/src/types.rs | 372 +++++++++++++++--- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 2 + .../extern/extern-C-str-arg-ice-80125.stderr | 2 + tests/ui/lint/extern-C-fnptr-lints-slices.rs | 2 +- .../lint/extern-C-fnptr-lints-slices.stderr | 7 +- tests/ui/lint/lint-ctypes-73249-2.stderr | 1 + tests/ui/lint/lint-ctypes-94223.stderr | 26 +- tests/ui/lint/lint-ctypes-cstr.rs | 4 +- tests/ui/lint/lint-ctypes-cstr.stderr | 11 +- tests/ui/lint/lint-ctypes-fn.rs | 6 +- tests/ui/lint/lint-ctypes-fn.stderr | 22 +- tests/ui/lint/lint-ctypes.rs | 35 +- tests/ui/lint/lint-ctypes.stderr | 123 +++--- 15 files changed, 490 insertions(+), 181 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 55c6a122d35d5..8d327a3a4a9e7 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -364,7 +364,6 @@ lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stab 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 @@ -382,7 +381,9 @@ lint_improper_ctypes_enum_repr_help = lint_improper_ctypes_enum_repr_reason = enum has no representation hint lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead +lint_improper_ctypes_fnptr_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}` lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention + lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants @@ -393,7 +394,11 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent lint_improper_ctypes_pat_help = consider using the base type instead lint_improper_ctypes_pat_reason = pattern types have no C equivalent -lint_improper_ctypes_slice_help = consider using a raw pointer instead + +lint_improper_ctypes_sized_ptr_to_unsafe_type = + this reference (`{$ty}`) is ABI-compatible with a C pointer, but `{$inner_ty}` itself does not have a C layout + +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 @@ -419,6 +424,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + lint_incomplete_include = include macro expected single expression in source diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 09b0e1ed8bdba..107b9b1a4cefc 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1873,13 +1873,44 @@ 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_with>( + self, + diag: &mut Diag<'_, G>, + f: &F, + ) { + diag.arg("ty", self.ty); + if let Some(ty) = self.inner_ty { + diag.arg("inner_ty", ty); + } + + if let Some(help) = self.help { + let msg = f(diag, help.into()); + diag.help(msg); + } + + let msg = f(diag, self.note.into()); + diag.note(msg); + if let Some(note) = self.span_note { + let msg = f(diag, fluent::lint_note.into()); + diag.span_note(note, msg); + }; + } +} + 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 @@ -1889,12 +1920,8 @@ impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> { 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); } } } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 0060f33888ebd..fc8b64b3c709b 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -23,10 +23,10 @@ mod improper_ctypes; use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad, - AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons, - InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons, - UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons, UsesPowerAlignment, - VariantSizeDifferencesDiag, + AtomicOrderingStore, ImproperCTypes, ImproperCTypesLayer, InvalidAtomicOrderingDiag, + InvalidNanComparisons, InvalidNanComparisonsSuggestion, + UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion, + UnusedComparisons, UsesPowerAlignment, VariantSizeDifferencesDiag, }; use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; @@ -806,7 +806,109 @@ struct CTypesVisitorState<'tcx> { enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), - FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option }, + FfiUnsafe { + ty: Ty<'tcx>, + reason: DiagMessage, + help: Option, + }, + FfiUnsafeWrapper { + ty: Ty<'tcx>, + reason: DiagMessage, + help: Option, + wrapped: Box>, + }, +} + +/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it +#[derive(Clone, Copy)] +enum TypeSizedness { + /// type of definite size (pointers are C-compatible) + Definite, + /// unsized type because it includes an opaque/foreign type (pointers are C-compatible) + UnsizedWithExternType, + /// unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible) + UnsizedWithMetadata, +} + +/// Is this type unsized because it contains (or is) a foreign type? +/// (Returns Err if the type happens to be sized after all) +fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { + let tcx = cx.tcx; + + if ty.is_sized(tcx, cx.typing_env()) { + TypeSizedness::Definite + } else { + match ty.kind() { + ty::Slice(_) => TypeSizedness::UnsizedWithMetadata, + ty::Str => TypeSizedness::UnsizedWithMetadata, + ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata, + ty::Foreign(..) => TypeSizedness::UnsizedWithExternType, + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach this branch. + ty::Alias(ty::Opaque, ..) => todo!("We... don't know enough about this type yet?"), + ty::Adt(def, args) => { + // for now assume: boxes and phantoms don't mess with this + match def.adt_kind() { + AdtKind::Union | AdtKind::Enum => { + bug!("unions and enums are necessarily sized") + } + AdtKind::Struct => { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + { + return TypeSizedness::UnsizedWithMetadata; + } + // FIXME: how do we deal with non-exhaustive unsized structs/unions? + + 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 unsized + let n_fields = variant.fields.len(); + let last_field = &variant.fields[(n_fields - 1).into()]; + let field_ty = last_field.ty(cx.tcx, args); + let field_ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), field_ty) + .unwrap_or(field_ty); + match get_type_sizedness(cx, field_ty) { + s @ (TypeSizedness::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType) => s, + TypeSizedness::Definite => { + bug!("failed to find the reason why struct `{:?}` is unsized", ty) + } + } + } + } + } + ty::Tuple(tuple) => { + // only the last field may be unsized + let n_fields = tuple.len(); + let field_ty: Ty<'tcx> = tuple[n_fields - 1]; + //let field_ty = last_field.ty(cx.tcx, args); + let field_ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), field_ty) + .unwrap_or(field_ty); + match get_type_sizedness(cx, field_ty) { + s @ (TypeSizedness::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType) => s, + TypeSizedness::Definite => { + bug!("failed to find the reason why tuple `{:?}` is unsized", ty) + } + } + } + ty => { + bug!( + "we shouldn't be trying to determine if this is unsized for a reason or another: `{:?}`", + ty + ) + } + } + } } pub(crate) fn nonnull_optimization_guaranteed<'tcx>( @@ -843,7 +945,7 @@ fn ty_is_known_nonnull<'tcx>( match ty.kind() { ty::FnPtr(..) => true, ty::Ref(..) => true, - ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, + ty::Adt(def, _) if def.is_box() => true, ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => { let marked_non_null = nonnull_optimization_guaranteed(tcx, *def); @@ -1012,12 +1114,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Check if the type is array and emit an unsafe type lint. fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { if let ty::Array(..) = ty.kind() { - self.emit_ffi_unsafe_type_lint( + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { ty, - sp, - fluent::lint_improper_ctypes_array_reason, - Some(fluent::lint_improper_ctypes_array_help), - ); + note: fluent::lint_improper_ctypes_array_reason, + help: Some(fluent::lint_improper_ctypes_array_help), + inner_ty: None, + span_note: None, + }]); true } else { false @@ -1074,9 +1177,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { all_phantom &= match self.check_field_type_for_ffi(acc, field, args) { FfiSafe => false, // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } if ty.is_unit() => false, + FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, FfiPhantom(..) => true, - r @ FfiUnsafe { .. } => return r, + r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, } } @@ -1110,16 +1213,47 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match *ty.kind() { ty::Adt(def, args) => { - if let Some(boxed) = ty.boxed_ty() - && matches!(self.mode, CItemKind::Definition) - { - if boxed.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; + if let Some(inner_ty) = ty.boxed_ty() { + if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite = + get_type_sizedness(self.cx, inner_ty) + { + // discussion on declaration vs definition: + // see the `ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _)` arm + // of this `match *ty.kind()` block + if matches!(self.mode, CItemKind::Definition) { + return FfiSafe; + } else { + let inner_res = self.check_type_for_ffi(acc, inner_ty); + return match inner_res { + FfiUnsafe { .. } | FfiUnsafeWrapper { .. } => FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, + wrapped: Box::new(inner_res), + help: None, + }, + _ => inner_res, + }; + } } else { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + ty::Adt(def, _) + if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) + && matches!( + tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + && !acc.base_ty.is_mutable_ptr() => + { + Some(fluent::lint_improper_ctypes_cstr_help) + } + _ => None, + }; return FfiUnsafe { ty, - reason: fluent::lint_improper_ctypes_box, - help: None, + reason: fluent::lint_improper_ctypes_unsized_box, + help, }; } } @@ -1275,15 +1409,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(fluent::lint_improper_ctypes_tuple_help), }, - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) - if { - matches!(self.mode, CItemKind::Definition) - && ty.is_sized(self.cx.tcx, self.cx.typing_env()) - } => - { - FfiSafe - } - ty::RawPtr(ty, _) if match ty.kind() { ty::Tuple(tuple) => tuple.is_empty(), @@ -1293,7 +1418,70 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty), + ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _) => { + if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite = + get_type_sizedness(self.cx, inner_ty) + { + // there's a nuance on what this lint should do for + // function definitions (`extern "C" fn fn_name(...) {...}`) + // versus declarations (`unsafe extern "C" {fn fn_name(...);}`). + // This is touched upon in https://github.com/rust-lang/rust/issues/66220 + // and https://github.com/rust-lang/rust/pull/72700 + // + // The big question is: what does "ABI safety" mean? if you have something translated to a C pointer + // (which has a stable layout) but points to FFI-unsafe type, is it safe? + // On one hand, the function's ABI will match that of a similar C-declared function API, + // on the other, dereferencing the pointer on the other side of the FFI boundary will be painful. + // In this code, the opinion on is split between function declarations and function definitions, + // with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type. + // For declarations, we see this as unsafe, but for definitions, we see this as safe. + // + // For extern function declarations, the actual definition of the function is written somewhere else, + // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) + // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, + // and having the full type information is necessary to compile the function. + if matches!(self.mode, CItemKind::Definition) { + return FfiSafe; + } else if matches!(ty.kind(), ty::RawPtr(..)) + && matches!(inner_ty.kind(), ty::Tuple(tuple) if tuple.is_empty()) + { + FfiSafe + } else { + let inner_res = self.check_type_for_ffi(acc, inner_ty); + return match inner_res { + FfiSafe => inner_res, + _ => FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, + wrapped: Box::new(inner_res), + help: None, + }, + }; + } + } else { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + ty::Adt(def, _) + if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) + && matches!( + tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + && !acc.base_ty.is_mutable_ptr() => + { + Some(fluent::lint_improper_ctypes_cstr_help) + } + _ => None, + }; + let reason = match ty.kind() { + ty::RawPtr(..) => fluent::lint_improper_ctypes_unsized_ptr, + ty::Ref(..) => fluent::lint_improper_ctypes_unsized_ref, + _ => unreachable!(), + }; + FfiUnsafe { ty, reason, help } + } + } ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), @@ -1311,7 +1499,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { for arg in sig.inputs() { match self.check_type_for_ffi(acc, *arg) { FfiSafe => {} - r => return r, + r => { + return FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }; + } } } @@ -1320,7 +1515,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return FfiSafe; } - self.check_type_for_ffi(acc, ret_ty) + match self.check_type_for_ffi(acc, ret_ty) { + r @ (FfiSafe | FfiPhantom(_)) => r, + r => FfiUnsafeWrapper { + ty: ty.clone(), + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }, + } } ty::Foreign(..) => FfiSafe, @@ -1359,8 +1562,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { &mut self, ty: Ty<'tcx>, sp: Span, - note: DiagMessage, - help: Option, + mut reasons: Vec>, ) { let lint = match self.mode { CItemKind::Declaration => IMPROPER_CTYPES, @@ -1370,21 +1572,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { CItemKind::Declaration => "block", CItemKind::Definition => "fn", }; - let span_note = if let ty::Adt(def, _) = ty.kind() - && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) - { - Some(sp) - } else { - None - }; - self.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) = self.cx.tcx.hir().span_if_local(def.did()) + { + Some(sp) + } else { + None + }; + } + + self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); } fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { @@ -1413,7 +1611,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .visit_with(&mut ProhibitOpaqueTypes) .break_value() { - self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint_improper_ctypes_opaque, None); + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_opaque, + span_note: Some(sp), + help: None, + inner_ty: None, + }]); true } else { false @@ -1452,15 +1656,71 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match self.check_type_for_ffi(&mut acc, ty) { FfiResult::FfiSafe => {} FfiResult::FfiPhantom(ty) => { - self.emit_ffi_unsafe_type_lint( + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { ty, - sp, - fluent::lint_improper_ctypes_only_phantomdata, - None, - ); + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + help: None, + inner_ty: None, + }]); } FfiResult::FfiUnsafe { ty, reason, help } => { - self.emit_ffi_unsafe_type_lint(ty, sp, reason, help); + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + help, + note: reason, + span_note: None, // filled later + inner_ty: None, + }]); + } + ffir @ FfiResult::FfiUnsafeWrapper { .. } => { + let mut ffiresult_recursor = ControlFlow::Continue(&ffir); + let mut cimproper_layers: Vec> = vec![]; + + // this whole while block converts the arbitrarily-deep + // FfiResult stack to an ImproperCTypesLayer Vec + while let ControlFlow::Continue(ref ffir_rec) = ffiresult_recursor { + match ffir_rec { + FfiResult::FfiPhantom(ty) => { + 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: None, + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + }); + ffiresult_recursor = ControlFlow::Break(()); + } + FfiResult::FfiUnsafe { ty, reason, help } + | FfiResult::FfiUnsafeWrapper { ty, reason, help, .. } => { + 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: reason.clone(), + span_note: None, // filled later + }); + + if let FfiResult::FfiUnsafeWrapper { wrapped, .. } = ffir_rec { + ffiresult_recursor = ControlFlow::Continue(wrapped.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } + } + FfiResult::FfiSafe => { + bug!("malformed FfiResult stack: it should be unsafe all the way down") + } + }; + } + // should always have at least one type + let last_ty = cimproper_layers.last().unwrap().ty.clone(); + self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers); } } } 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..b5c718ec38147 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -4,6 +4,7 @@ warning: `extern` fn uses type `CStr`, which is not FFI-safe LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr` = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes_definitions)]` on by default @@ -14,6 +15,7 @@ warning: `extern` block uses type `CStr`, which is not FFI-safe LL | fn meh(blah: Foo); | ^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr` = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` on by default 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..f2ee21c316658 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -4,6 +4,7 @@ warning: `extern` fn uses type `str`, which is not FFI-safe LL | type ExternCallback = extern "C" fn(*const u8, u32, str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(*const u8, u32, str)` is FFI-unsafe due to `str` = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent = note: `#[warn(improper_ctypes_definitions)]` on by default @@ -14,6 +15,7 @@ warning: `extern` fn uses type `str`, which is not FFI-safe LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | ^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(*const u8, u32, str)` is FFI-unsafe due to `str` = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 0c35eb37a4890..4e3832ab1b672 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.rs +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.rs @@ -3,7 +3,7 @@ // It's an improper ctype (a slice) arg in an extern "C" fnptr. pub type F = extern "C" fn(&[u8]); -//~^ ERROR: `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR: `extern` fn 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..c0923dd96c8c3 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,11 +1,12 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` fn uses type `&[u8]`, which is not FFI-safe --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 | LL | pub type F = extern "C" fn(&[u8]); | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = note: the function pointer to `for<'a> extern "C" fn(&'a [u8])` is FFI-unsafe due to `&[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 | diff --git a/tests/ui/lint/lint-ctypes-73249-2.stderr b/tests/ui/lint/lint-ctypes-73249-2.stderr index ef30a406969d3..f035cdb213efe 100644 --- a/tests/ui/lint/lint-ctypes-73249-2.stderr +++ b/tests/ui/lint/lint-ctypes-73249-2.stderr @@ -4,6 +4,7 @@ error: `extern` block uses type `Qux`, which is not FFI-safe LL | fn lint_me() -> A<()>; | ^^^^^ not FFI-safe | + = note: this reference (`&Qux`) is ABI-compatible with a C pointer, but `Qux` itself does not have a C layout = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-ctypes-73249-2.rs:2:9 diff --git a/tests/ui/lint/lint-ctypes-94223.stderr b/tests/ui/lint/lint-ctypes-94223.stderr index bd127cf60044c..4bebca69b7f3b 100644 --- a/tests/ui/lint/lint-ctypes-94223.stderr +++ b/tests/ui/lint/lint-ctypes-94223.stderr @@ -4,7 +4,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent note: the lint level is defined here --> $DIR/lint-ctypes-94223.rs:2:9 @@ -18,7 +19,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `[u8]`, which is not FFI-safe @@ -27,7 +29,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `[u8]`, which is not FFI-safe @@ -36,7 +39,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | struct BadStruct(extern "C" fn([u8])); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `[u8]`, which is not FFI-safe @@ -45,7 +49,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `[u8]`, which is not FFI-safe @@ -54,7 +59,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `[u8]`, which is not FFI-safe @@ -63,7 +69,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe LL | type Foo = extern "C" fn([u8]); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` fn uses type `Option<&::FooType>`, which is not FFI-safe @@ -72,6 +79,7 @@ error: `extern` fn uses type `Option<&::FooType>`, which is not F LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `for<'a> extern "C" fn(Option<&'a ::FooType>)` is FFI-unsafe due to `Option<&::FooType>` = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint @@ -81,6 +89,7 @@ error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -95,6 +104,7 @@ error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -109,6 +119,7 @@ error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -123,6 +134,7 @@ error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here diff --git a/tests/ui/lint/lint-ctypes-cstr.rs b/tests/ui/lint/lint-ctypes-cstr.rs index b04decd0bcacc..c4de5a44a9623 100644 --- a/tests/ui/lint/lint-ctypes-cstr.rs +++ b/tests/ui/lint/lint-ctypes-cstr.rs @@ -8,7 +8,7 @@ extern "C" { //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` fn take_cstr_ref(s: &CStr); - //~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe + //~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` fn take_cstring(s: CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe @@ -27,7 +27,7 @@ extern "C" { } extern "C" fn rust_take_cstr_ref(s: &CStr) {} -//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe +//~^ ERROR `extern` fn uses type `&CStr`, which is not FFI-safe //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` extern "C" fn rust_take_cstring(s: CString) {} //~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe diff --git a/tests/ui/lint/lint-ctypes-cstr.stderr b/tests/ui/lint/lint-ctypes-cstr.stderr index 8957758d57732..da15b748f2110 100644 --- a/tests/ui/lint/lint-ctypes-cstr.stderr +++ b/tests/ui/lint/lint-ctypes-cstr.stderr @@ -12,14 +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-ctypes-cstr.rs:10:25 | LL | fn take_cstr_ref(s: &CStr); | ^^^^^ not FFI-safe | = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` - = note: `CStr`/`CString` do not have a guaranteed layout + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `CString`, which is not FFI-safe --> $DIR/lint-ctypes-cstr.rs:13:24 @@ -36,6 +36,7 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn take_cstring_ref(s: &CString); | ^^^^^^^^ not FFI-safe | + = note: this reference (`&CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout @@ -45,6 +46,7 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn no_special_help_for_mut_cstring(s: *mut CString); | ^^^^^^^^^^^^ not FFI-safe | + = note: this reference (`*mut CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -54,17 +56,18 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString); | ^^^^^^^^^^^^ not FFI-safe | + = note: this reference (`&mut CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout -error: `extern` fn uses type `CStr`, which is not FFI-safe +error: `extern` fn uses type `&CStr`, which is not FFI-safe --> $DIR/lint-ctypes-cstr.rs:29:37 | LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {} | ^^^^^ not FFI-safe | = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` - = note: `CStr`/`CString` do not have a guaranteed layout + = note: 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-cstr.rs:2:26 | diff --git a/tests/ui/lint/lint-ctypes-fn.rs b/tests/ui/lint/lint-ctypes-fn.rs index 73820c86d1a02..e16ff9573fd18 100644 --- a/tests/ui/lint/lint-ctypes-fn.rs +++ b/tests/ui/lint/lint-ctypes-fn.rs @@ -68,10 +68,10 @@ 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) { } @@ -124,7 +124,7 @@ pub extern "C" fn transparent_i128(p: TransparentI128) { } //~^ ERROR: uses type `i128` pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } diff --git a/tests/ui/lint/lint-ctypes-fn.stderr b/tests/ui/lint/lint-ctypes-fn.stderr index a62533a4be17b..c86c02c80064c 100644 --- a/tests/ui/lint/lint-ctypes-fn.stderr +++ b/tests/ui/lint/lint-ctypes-fn.stderr @@ -1,25 +1,25 @@ -error: `extern` fn uses type `[u32]`, which is not FFI-safe +error: `extern` fn uses type `&[u32]`, which is not FFI-safe --> $DIR/lint-ctypes-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-ctypes-fn.rs:2:9 | LL | #![deny(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-ctypes-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-ctypes-fn.rs:80:34 @@ -27,7 +27,8 @@ error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe | - = 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-ctypes-fn.rs:83:35 @@ -35,7 +36,8 @@ error: `extern` fn uses type `Box`, which is not FFI-safe 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-ctypes-fn.rs:86:34 @@ -43,7 +45,7 @@ error: `extern` fn uses type `Box`, which is not FFI-safe 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-ctypes-fn.rs:89:32 @@ -149,14 +151,14 @@ LL | pub extern "C" fn transparent_i128(p: TransparentI128) { } | = note: 128-bit integers don't currently have a known stable ABI -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-ctypes-fn.rs:126:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `PhantomData`, which is not FFI-safe --> $DIR/lint-ctypes-fn.rs:172:43 diff --git a/tests/ui/lint/lint-ctypes.rs b/tests/ui/lint/lint-ctypes.rs index 6dd9be10a48f8..22617e0ebb99b 100644 --- a/tests/ui/lint/lint-ctypes.rs +++ b/tests/ui/lint/lint-ctypes.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +#![feature(extern_types)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -6,7 +7,9 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; +use std::fmt::Debug; +unsafe extern "C" {type UnsizedOpaque;} trait Bar { } trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } @@ -20,7 +23,7 @@ pub type I32Pair = (i32, i32); #[repr(C)] pub struct ZeroSize; pub type RustFn = fn(); -pub type RustBadRet = extern "C" fn() -> Box; +pub type RustBoxRet = extern "C" fn() -> Box; pub type CVoidRet = (); pub struct Foo; #[repr(transparent)] @@ -28,7 +31,7 @@ pub struct TransparentI128(i128); #[repr(transparent)] pub struct TransparentStr(&'static str); #[repr(transparent)] -pub struct TransparentBadFn(RustBadRet); +pub struct TransparentBoxFn(RustBoxRet); #[repr(transparent)] pub struct TransparentInt(u32); #[repr(transparent)] @@ -39,6 +42,16 @@ 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 ZeroSizeWithPhantomData(::std::marker::PhantomData); @@ -48,15 +61,14 @@ extern "C" { pub fn ptr_type2(size: *const Foo); //~ ERROR: uses type `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 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>); - //~^ ERROR uses type `Option>` pub fn char_type(p: char); //~ ERROR uses type `char` pub fn i128_type(p: i128); //~ ERROR uses type `i128` pub fn u128_type(p: u128); //~ ERROR uses type `u128` - pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `dyn Bar` + pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `&dyn Bar` pub fn tuple_type(p: (i32, i32)); //~ ERROR uses type `(i32, i32)` pub fn tuple_type2(p: I32Pair); //~ ERROR uses type `(i32, i32)` pub fn zero_size(p: ZeroSize); //~ ERROR uses type `ZeroSize` @@ -66,12 +78,15 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); //~ ERROR: uses type `Box` + pub fn fn_contained(p: RustBoxRet); pub fn transparent_i128(p: TransparentI128); //~ ERROR: uses type `i128` - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: uses type `Box` + pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` + pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` + pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); + pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` + pub fn no_niche_a(a: Option>); //~^ ERROR: uses type `Option>` pub fn no_niche_b(b: Option>); diff --git a/tests/ui/lint/lint-ctypes.stderr b/tests/ui/lint/lint-ctypes.stderr index 8137ae868d356..9ec17b1d94b32 100644 --- a/tests/ui/lint/lint-ctypes.stderr +++ b/tests/ui/lint/lint-ctypes.stderr @@ -1,83 +1,68 @@ error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:47:28 + --> $DIR/lint-ctypes.rs:60:28 | LL | pub fn ptr_type1(size: *const Foo); | ^^^^^^^^^^ not FFI-safe | + = note: this reference (`*const Foo`) is ABI-compatible with a C pointer, but `Foo` itself does not have a C layout = 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 + --> $DIR/lint-ctypes.rs:28:1 | LL | pub struct Foo; | ^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint-ctypes.rs:4:9 + --> $DIR/lint-ctypes.rs:5:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:48:28 + --> $DIR/lint-ctypes.rs:61:28 | LL | pub fn ptr_type2(size: *const Foo); | ^^^^^^^^^^ not FFI-safe | + = note: this reference (`*const Foo`) is ABI-compatible with a C pointer, but `Foo` itself does not have a C layout = 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 + --> $DIR/lint-ctypes.rs:28:1 | LL | pub struct Foo; | ^^^^^^^^^^^^^^ error: `extern` block uses type `((),)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:50:25 + --> $DIR/lint-ctypes.rs:63:25 | LL | pub fn ptr_tuple(p: *const ((),)); | ^^^^^^^^^^^^ not FFI-safe | + = note: this reference (`*const ((),)`) is ABI-compatible with a C pointer, but `((),)` itself does not have a C layout = help: consider using a struct instead = note: tuples have unspecified layout -error: `extern` block uses type `[u32]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:51:26 +error: `extern` block uses type `&[u32]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:64:26 | LL | pub 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 -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:65: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 - -error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:54:28 - | -LL | pub fn opt_box_type(p: 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 + = 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:56:25 + --> $DIR/lint-ctypes.rs:68:25 | LL | pub fn char_type(p: char); | ^^^^ not FFI-safe @@ -86,7 +71,7 @@ LL | pub fn char_type(p: char); = note: the `char` type has no C equivalent error: `extern` block uses type `i128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:57:25 + --> $DIR/lint-ctypes.rs:69:25 | LL | pub fn i128_type(p: i128); | ^^^^ not FFI-safe @@ -94,23 +79,23 @@ LL | pub fn i128_type(p: i128); = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:58:25 + --> $DIR/lint-ctypes.rs:70:25 | LL | pub fn u128_type(p: u128); | ^^^^ not FFI-safe | = note: 128-bit integers don't currently have a known stable ABI -error: `extern` block uses type `dyn Bar`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:59:26 +error: `extern` block uses type `&dyn Bar`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:71: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:60:26 + --> $DIR/lint-ctypes.rs:72:26 | LL | pub fn tuple_type(p: (i32, i32)); | ^^^^^^^^^^ not FFI-safe @@ -119,7 +104,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:61:27 + --> $DIR/lint-ctypes.rs:73:27 | LL | pub fn tuple_type2(p: I32Pair); | ^^^^^^^ not FFI-safe @@ -128,7 +113,7 @@ LL | pub fn tuple_type2(p: I32Pair); = note: tuples have unspecified layout error: `extern` block uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:62:25 + --> $DIR/lint-ctypes.rs:74:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -136,26 +121,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: this struct has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:21:1 + --> $DIR/lint-ctypes.rs:24:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:63:33 + --> $DIR/lint-ctypes.rs:75: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:57:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:66:12 + --> $DIR/lint-ctypes.rs:78:12 | LL | -> ::std::marker::PhantomData; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -163,7 +148,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:67:23 + --> $DIR/lint-ctypes.rs:79:23 | LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe @@ -172,7 +157,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:68:24 + --> $DIR/lint-ctypes.rs:80:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -180,43 +165,25 @@ 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:69: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 `i128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:70:32 + --> $DIR/lint-ctypes.rs:82:32 | LL | pub fn transparent_i128(p: TransparentI128); | ^^^^^^^^^^^^^^^ not FFI-safe | = note: 128-bit integers don't currently have a known stable ABI -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:31 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:83: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:72:30 - | -LL | pub fn transparent_fn(p: TransparentBadFn); - | ^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:73:27 + --> $DIR/lint-ctypes.rs:85:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -224,8 +191,16 @@ LL | pub fn raw_array(arr: [u8; 8]); = help: consider passing a pointer to the array = note: passing raw arrays by value is not FFI-safe +error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:88: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:75:26 + --> $DIR/lint-ctypes.rs:90:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -234,7 +209,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:77:26 + --> $DIR/lint-ctypes.rs:92:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -243,7 +218,7 @@ LL | pub fn no_niche_b(b: Option>); = note: enum has no representation hint error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:80:34 + --> $DIR/lint-ctypes.rs:95:34 | LL | pub static static_u128_type: u128; | ^^^^ not FFI-safe @@ -251,12 +226,12 @@ LL | pub static static_u128_type: u128; = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:81:40 + --> $DIR/lint-ctypes.rs:96:40 | LL | pub static static_u128_array_type: [u128; 16]; | ^^^^^^^^^^ not FFI-safe | = note: 128-bit integers don't currently have a known stable ABI -error: aborting due to 27 previous errors +error: aborting due to 24 previous errors From 40eb231d29ab05647605561198a9dd1bb48fd074 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Mon, 23 Dec 2024 11:56:55 +0100 Subject: [PATCH 2/9] lint ImproperCTypes: make checking indirection more DRY [commit does not pass tests] --- compiler/rustc_lint/src/types.rs | 193 ++++++++++++++----------------- 1 file changed, 88 insertions(+), 105 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index fc8b64b3c709b..8293135d82800 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -830,6 +830,17 @@ enum TypeSizedness { UnsizedWithMetadata, } +/// what type indirection points to a given type +#[derive(Clone, Copy)] +enum IndirectionType { + /// 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, +} + /// Is this type unsized because it contains (or is) a foreign type? /// (Returns Err if the type happens to be sized after all) fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { @@ -1192,6 +1203,77 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" + fn check_indirection_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + inner_ty: Ty<'tcx>, + indirection_type: IndirectionType, + ) -> FfiResult<'tcx> { + use FfiResult::*; + let tcx = self.cx.tcx; + match get_type_sizedness(self.cx, inner_ty) { + TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { + // there's a nuance on what this lint should do for + // function definitions (`extern "C" fn fn_name(...) {...}`) + // versus declarations (`unsafe extern "C" {fn fn_name(...);}`). + // This is touched upon in https://github.com/rust-lang/rust/issues/66220 + // and https://github.com/rust-lang/rust/pull/72700 + // + // The big question is: what does "ABI safety" mean? if you have something translated to a C pointer + // (which has a stable layout) but points to FFI-unsafe type, is it safe? + // On one hand, the function's ABI will match that of a similar C-declared function API, + // on the other, dereferencing the pointer on the other side of the FFI boundary will be painful. + // In this code, the opinion on is split between function declarations and function definitions, + // with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type. + // For declarations, we see this as unsafe, but for definitions, we see this as safe. + // + // For extern function declarations, the actual definition of the function is written somewhere else, + // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) + // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, + // and having the full type information is necessary to compile the function. + if matches!(self.mode, CItemKind::Definition) { + return FfiSafe; + } else { + let inner_res = self.check_type_for_ffi(acc, inner_ty); + return match inner_res { + FfiSafe => inner_res, + _ => FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, + wrapped: Box::new(inner_res), + help: None, + }, + }; + } + } + TypeSizedness::UnsizedWithMetadata => { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + ty::Adt(def, _) + if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) + && matches!( + tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + && !acc.base_ty.is_mutable_ptr() => + { + Some(fluent::lint_improper_ctypes_cstr_help) + } + _ => None, + }; + let reason = match indirection_type { + IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, + }; + FfiUnsafe { ty, reason, help } + } + } + } + /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). fn check_type_for_ffi( @@ -1214,48 +1296,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match *ty.kind() { ty::Adt(def, args) => { if let Some(inner_ty) = ty.boxed_ty() { - if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite = - get_type_sizedness(self.cx, inner_ty) - { - // discussion on declaration vs definition: - // see the `ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _)` arm - // of this `match *ty.kind()` block - if matches!(self.mode, CItemKind::Definition) { - return FfiSafe; - } else { - let inner_res = self.check_type_for_ffi(acc, inner_ty); - return match inner_res { - FfiUnsafe { .. } | FfiUnsafeWrapper { .. } => FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, - wrapped: Box::new(inner_res), - help: None, - }, - _ => inner_res, - }; - } - } else { - let help = match inner_ty.kind() { - ty::Str => Some(fluent::lint_improper_ctypes_str_help), - ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), - ty::Adt(def, _) - if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) - && matches!( - tcx.get_diagnostic_name(def.did()), - Some(sym::cstring_type | sym::cstr_type) - ) - && !acc.base_ty.is_mutable_ptr() => - { - Some(fluent::lint_improper_ctypes_cstr_help) - } - _ => None, - }; - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_unsized_box, - help, - }; - } + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Box); } if def.is_phantom_data() { return FfiPhantom(ty); @@ -1418,69 +1459,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _) => { - if let TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite = - get_type_sizedness(self.cx, inner_ty) - { - // there's a nuance on what this lint should do for - // function definitions (`extern "C" fn fn_name(...) {...}`) - // versus declarations (`unsafe extern "C" {fn fn_name(...);}`). - // This is touched upon in https://github.com/rust-lang/rust/issues/66220 - // and https://github.com/rust-lang/rust/pull/72700 - // - // The big question is: what does "ABI safety" mean? if you have something translated to a C pointer - // (which has a stable layout) but points to FFI-unsafe type, is it safe? - // On one hand, the function's ABI will match that of a similar C-declared function API, - // on the other, dereferencing the pointer on the other side of the FFI boundary will be painful. - // In this code, the opinion on is split between function declarations and function definitions, - // with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type. - // For declarations, we see this as unsafe, but for definitions, we see this as safe. - // - // For extern function declarations, the actual definition of the function is written somewhere else, - // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) - // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, - // and having the full type information is necessary to compile the function. - if matches!(self.mode, CItemKind::Definition) { - return FfiSafe; - } else if matches!(ty.kind(), ty::RawPtr(..)) - && matches!(inner_ty.kind(), ty::Tuple(tuple) if tuple.is_empty()) - { - FfiSafe - } else { - let inner_res = self.check_type_for_ffi(acc, inner_ty); - return match inner_res { - FfiSafe => inner_res, - _ => FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, - wrapped: Box::new(inner_res), - help: None, - }, - }; - } - } else { - let help = match inner_ty.kind() { - ty::Str => Some(fluent::lint_improper_ctypes_str_help), - ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), - ty::Adt(def, _) - if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) - && matches!( - tcx.get_diagnostic_name(def.did()), - Some(sym::cstring_type | sym::cstr_type) - ) - && !acc.base_ty.is_mutable_ptr() => - { - Some(fluent::lint_improper_ctypes_cstr_help) - } - _ => None, - }; - let reason = match ty.kind() { - ty::RawPtr(..) => fluent::lint_improper_ctypes_unsized_ptr, - ty::Ref(..) => fluent::lint_improper_ctypes_unsized_ref, - _ => unreachable!(), - }; - FfiUnsafe { ty, reason, help } - } + ty::RawPtr(inner_ty, _) => { + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::RawPtr); + } + ty::Ref(_, inner_ty, _) => { + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Ref); } ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), From e843e7aeb140e26ece19a1ece693f8f7ca805339 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Mon, 23 Dec 2024 15:14:16 +0100 Subject: [PATCH 3/9] lint ImproperCTypes: fix TypeSizedness code --- compiler/rustc_lint/src/types.rs | 86 ++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 8293135d82800..1e3d557b58412 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -828,6 +828,8 @@ enum TypeSizedness { UnsizedWithExternType, /// unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible) UnsizedWithMetadata, + /// not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like) + NotYetKnown, } /// what type indirection points to a given type @@ -846,17 +848,16 @@ enum IndirectionType { fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { let tcx = cx.tcx; + // note that sizedness is unrelated to inhabitedness if ty.is_sized(tcx, cx.typing_env()) { TypeSizedness::Definite } else { + // the overall type is !Sized or ?Sized match ty.kind() { ty::Slice(_) => TypeSizedness::UnsizedWithMetadata, ty::Str => TypeSizedness::UnsizedWithMetadata, ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata, ty::Foreign(..) => TypeSizedness::UnsizedWithExternType, - // While opaque types are checked for earlier, if a projection in a struct field - // normalizes to an opaque type, then it will reach this branch. - ty::Alias(ty::Opaque, ..) => todo!("We... don't know enough about this type yet?"), ty::Adt(def, args) => { // for now assume: boxes and phantoms don't mess with this match def.adt_kind() { @@ -869,7 +870,13 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type { return TypeSizedness::UnsizedWithMetadata; } - // FIXME: how do we deal with non-exhaustive unsized structs/unions? + + // FIXME: double-check: non-exhaustive structs from other crates are assumed to be ?Sized, right? + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did().is_local() { + return TypeSizedness::NotYetKnown; + } if def.non_enum_variant().fields.is_empty() { bug!("an empty struct is necessarily sized"); @@ -877,7 +884,7 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type let variant = def.non_enum_variant(); - // only the last field may be unsized + // only the last field may be !Sized (or ?Sized in the case of type params) let n_fields = variant.fields.len(); let last_field = &variant.fields[(n_fields - 1).into()]; let field_ty = last_field.ty(cx.tcx, args); @@ -887,7 +894,8 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type .unwrap_or(field_ty); match get_type_sizedness(cx, field_ty) { s @ (TypeSizedness::UnsizedWithMetadata - | TypeSizedness::UnsizedWithExternType) => s, + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, TypeSizedness::Definite => { bug!("failed to find the reason why struct `{:?}` is unsized", ty) } @@ -896,7 +904,7 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type } } ty::Tuple(tuple) => { - // only the last field may be unsized + // only the last field may be !Sized (or ?Sized in the case of type params) let n_fields = tuple.len(); let field_ty: Ty<'tcx> = tuple[n_fields - 1]; //let field_ty = last_field.ty(cx.tcx, args); @@ -906,18 +914,49 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type .unwrap_or(field_ty); match get_type_sizedness(cx, field_ty) { s @ (TypeSizedness::UnsizedWithMetadata - | TypeSizedness::UnsizedWithExternType) => s, + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, TypeSizedness::Definite => { bug!("failed to find the reason why tuple `{:?}` is unsized", ty) } } } - ty => { + + ty_kind @ (ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Array(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) + | ty::Never + | ty::Pat(..) // these are (for now) numeric types with a range-based restriction + ) => { + // those types are all sized, right? bug!( - "we shouldn't be trying to determine if this is unsized for a reason or another: `{:?}`", - ty + "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::Alias(ty::Weak, ..) + | 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), } } } @@ -1248,6 +1287,26 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } } + 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 + // (FIXME note: function 'declarations' there should be treated as definitions) + // - 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) + // + // FIXME: on a side note, we should separate 'true' declarations (non-rust code), + // 'fake' declarations (in traits, needed to be implemented elsewhere), and definitions. + // (for instance, definitions should worry about &self with Self:?Sized, but fake declarations shouldn't) + + // wether 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. + return self.check_type_for_ffi(acc, inner_ty); + } TypeSizedness::UnsizedWithMetadata => { let help = match inner_ty.kind() { ty::Str => Some(fluent::lint_improper_ctypes_str_help), @@ -1421,6 +1480,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(fluent::lint_improper_ctypes_pat_help), }, + // FIXME: this should probably be architecture-dependant + // same with some ty::Float variants. ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None } } @@ -1466,6 +1527,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Ref); } + // having arrays as arguments / return values themselves is not FFI safe, + // but that is checked elsewhere + // if we reach this, we can assume the array is inside a struct, behind an indirection, etc. ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), ty::FnPtr(sig_tys, hdr) => { From 90ca86a9e3c91bdb75ccc67a7efe889524c4894f Mon Sep 17 00:00:00 2001 From: niacdoial Date: Mon, 23 Dec 2024 15:17:49 +0100 Subject: [PATCH 4/9] lint ImproperCTypes: add test to cover all ty_kinds --- tests/ui/lint/lint-ctypes-tykind-fuzz.rs | 311 ++++++++++++++++ tests/ui/lint/lint-ctypes-tykind-fuzz.stderr | 352 +++++++++++++++++++ 2 files changed, 663 insertions(+) create mode 100644 tests/ui/lint/lint-ctypes-tykind-fuzz.rs create mode 100644 tests/ui/lint/lint-ctypes-tykind-fuzz.stderr diff --git a/tests/ui/lint/lint-ctypes-tykind-fuzz.rs b/tests/ui/lint/lint-ctypes-tykind-fuzz.rs new file mode 100644 index 0000000000000..27bcfcf0e9a0c --- /dev/null +++ b/tests/ui/lint/lint-ctypes-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: &TemplateStruct + // Alias ...not Inherent. dangit +) -> TemplateStruct::Out { + 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..), //~ ERROR: uses 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:!, + //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 (this gets caught outside of the code we want to test) +) -> 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, //~ ERROR: uses type `String` + // Ptr[Str] + s2: *const str, //~ ERROR: uses type `*const str` + // Ptr[Char] + c: *const char, //~ ERROR: uses type `char` + // Ptr[Slice] + s3: *const [u8], //~ ERROR: uses type `*const [u8]` + // deactivated here, because this is a function *declaration* (param N unacceptable) + // s4: *const [u8;N], + // Ptr[Tuple] + p: *const (u8,u8), //~ ERROR: uses type `(u8, u8)` + // deactivated here, because this is a function *declaration* (pattern unacceptable) + // (p2, p3):(*const u8, *const u8), + // Pat + nz: *const pattern_type!(u32 is 1..), //~ ERROR: uses type `(u32) is 1..=` + // deactivated here, because this is a function *declaration* (pattern unacceptable) + //SomeStruct{b: ref p4,..}: & SomeStruct, + // Ptr[Union] + u2: *const SomeUnion, + // Ptr[Enum], + e: *const SomeEnum, + // deactivated here, because this is a function *declaration* (impl type unacceptable) + //d: *const impl Clone, + // deactivated here, because this is a function *declaration* (type param unacceptable) + //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, //~ ERROR: uses type `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` + // deactivated here, because this is a function *declaration* (impl type unacceptable) + //a: *const impl async Fn(u8)->u8, + // Alias (this gets caught outside of the code we want to test) +) -> *const dyn std::fmt::Debug; //~ ERROR: uses type `*const dyn Debug` +} + +extern "C" fn all_ty_kinds_in_ref<'a, const N:usize,T>( + // 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]` + // Ref[Array] (this gets caught outside of the code we want to test) + s4: &[u8;N], + // Ref[Tuple] + p: &(u8, u8), + // also Tuple + (p2, p3):(&u8, &u8), //~ ERROR: uses type `(&u8, &u8)` + // Pat + nz: &pattern_type!(u32 is 1..), + // Ref[Struct] + SomeStruct{b: ref p4,..}: &SomeStruct, + // Ref[Union] + u2: &SomeUnion, + // Ref[Enum], + e: &SomeEnum, + // Ref[Param] + d: &impl Clone, + // Ref[Param] + 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` + // Ref[Param], + a: &impl async Fn(u8)->u8, + // Ref[Dynamic] (this gets caught outside of the code we want to test) +) -> &'a dyn std::fmt::Debug { //~ ERROR: uses type `&dyn Debug` + i +} + +extern "C" fn all_ty_kinds_in_box( + // Box[UInt], Box[Int], Box[Float], Box[Bool] + u: Box, i: Box, f: Box, b: Box, + // Box[Struct] + s: Box, + // 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: Box<[u8;N]>, + // Box[Tuple] + p: Box<(u8,u8)>, + // also Tuple + (p2,p3):(Box, Box), //~ ERROR: uses type `(Box, Box)` + // Pat + nz: Box, + // Ref[Struct] + SomeStruct{b: ref p4,..}: &SomeStruct, + // Box[Union] + u2: Box, + // Box[Enum], + e: Box, + // Box[Param] + d: Box, + // Box[Param] + t: Box, + // Box[Foreign] + e2: Box, + // 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>` + // Box[Param], + a: Boxu8>, + // Box[Dynamic] (this gets caught outside of the code we want to test) +) -> Box { //~ ERROR: uses type `Box` + i +} + +fn main() {} diff --git a/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr b/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr new file mode 100644 index 0000000000000..e807d76bd5264 --- /dev/null +++ b/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr @@ -0,0 +1,352 @@ +warning: the feature `inherent_associated_types` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/lint-ctypes-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-ctypes-tykind-fuzz.rs:119:5 + | +LL | s:String, + | ^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout +note: the lint level is defined here + --> $DIR/lint-ctypes-tykind-fuzz.rs:5:25 + | +LL | #![deny(improper_ctypes,improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:121:6 + | +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-ctypes-tykind-fuzz.rs:123:6 + | +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-ctypes-tykind-fuzz.rs:125:6 + | +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-ctypes-tykind-fuzz.rs:127:6 + | +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-ctypes-tykind-fuzz.rs:129:5 + | +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-ctypes-tykind-fuzz.rs:131:12 + | +LL | (p2, p3):(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `(u32) is 1..=`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:133:7 + | +LL | nz: pattern_type!(u32 is 1..), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using the base type instead + = note: pattern types have no C equivalent + +error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:147:7 + | +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 `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:152:7 + | +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-ctypes-tykind-fuzz.rs:154:7 + | +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-ctypes-tykind-fuzz.rs:156:7 + | +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-ctypes-tykind-fuzz.rs:160:6 + | +LL | ) -> impl std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: opaque types have no C equivalent + +error: `extern` block uses type `String`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:169:6 + | +LL | s: *const String, + | ^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`*const String`) is ABI-compatible with a C pointer, but `String` itself does not have a C layout + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout +note: the lint level is defined here + --> $DIR/lint-ctypes-tykind-fuzz.rs:5:9 + | +LL | #![deny(improper_ctypes,improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `*const str`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:171:7 + | +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` block uses type `char`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:173:6 + | +LL | c: *const char, + | ^^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`*const char`) is ABI-compatible with a C pointer, but `char` itself does not have a C layout + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` block uses type `*const [u8]`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:175:7 + | +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` block uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:179:6 + | +LL | p: *const (u8,u8), + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`*const (u8, u8)`) is ABI-compatible with a C pointer, but `(u8, u8)` itself does not have a C layout + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` block uses type `(u32) is 1..=`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:183:7 + | +LL | nz: *const pattern_type!(u32 is 1..), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`*const (u32) is 1..=`) is ABI-compatible with a C pointer, but `(u32) is 1..=` itself does not have a C layout + = help: consider using the base type instead + = note: pattern types have no C equivalent + +error: `extern` block uses type `*const StructWithDyn`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:197:7 + | +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` block uses type `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:202:7 + | +LL | f2: *const fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`*const fn(u8) -> u8`) is ABI-compatible with a C pointer, but `fn(u8) -> u8` itself does not have a C layout + = help: consider using an `extern fn(...) -> ...` function pointer instead + = note: this function pointer has Rust-specific calling convention + +error: `extern` block uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:204:7 + | +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` block uses type `*const dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:206:7 + | +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` block uses type `*const dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:210:6 + | +LL | ) -> *const dyn std::fmt::Debug; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:219:7 + | +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 `&[u8]`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:223:7 + | +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, &u8)`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:229:12 + | +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-ctypes-tykind-fuzz.rs:245:7 + | +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 `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:252:7 + | +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` fn uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:254:7 + | +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 `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:258: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-ctypes-tykind-fuzz.rs:268:7 + | +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-ctypes-tykind-fuzz.rs:272:7 + | +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-ctypes-tykind-fuzz.rs:278:11 + | +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-ctypes-tykind-fuzz.rs:294:7 + | +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-ctypes-tykind-fuzz.rs:301:7 + | +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-ctypes-tykind-fuzz.rs:303:7 + | +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-ctypes-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 38 previous errors; 1 warning emitted + From e9e09730b21e9a6b1563b32a80fbb06c18bdc31b Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sun, 29 Dec 2024 20:32:34 +0100 Subject: [PATCH 5/9] lint ImproperCtypes: move code to new place, prior to full rewrite --- compiler/rustc_lint/src/types.rs | 1080 +---------------- .../rustc_lint/src/types/improper_ctypes.rs | 1075 +++++++++++++++- 2 files changed, 1082 insertions(+), 1073 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 1e3d557b58412..675e704a36179 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1,34 +1,26 @@ use std::iter; -use std::ops::ControlFlow; -use rustc_abi::{BackendRepr, ExternAbi, TagEncoding, VariantIdx, Variants, WrappingRange}; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::DiagMessage; -use rustc_hir::intravisit::VisitorExt; -use rustc_hir::{AmbigArg, Expr, ExprKind, LangItem}; +use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; +use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_middle::bug; use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton}; -use rustc_middle::ty::{ - self, Adt, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, -}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; -use rustc_span::def_id::LocalDefId; use rustc_span::{Span, Symbol, source_map, sym}; use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; -mod improper_ctypes; +mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad, - AtomicOrderingStore, ImproperCTypes, ImproperCTypesLayer, InvalidAtomicOrderingDiag, - InvalidNanComparisons, InvalidNanComparisonsSuggestion, - UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion, - UnusedComparisons, UsesPowerAlignment, VariantSizeDifferencesDiag, + AtomicOrderingStore, InvalidAtomicOrderingDiag, InvalidNanComparisons, + InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons, + UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons, + VariantSizeDifferencesDiag, }; -use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; +use crate::{LateContext, LateLintPass, LintContext}; mod literal; @@ -790,177 +782,6 @@ pub(crate) enum CItemKind { Definition, } -struct ImproperCTypesVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - mode: CItemKind, -} - -/// Accumulator for recursive ffi type checking -struct CTypesVisitorState<'tcx> { - cache: FxHashSet>, - /// The original type being checked, before we recursed - /// to any other types it contains. - base_ty: Ty<'tcx>, -} - -enum FfiResult<'tcx> { - FfiSafe, - FfiPhantom(Ty<'tcx>), - FfiUnsafe { - ty: Ty<'tcx>, - reason: DiagMessage, - help: Option, - }, - FfiUnsafeWrapper { - ty: Ty<'tcx>, - reason: DiagMessage, - help: Option, - wrapped: Box>, - }, -} - -/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it -#[derive(Clone, Copy)] -enum TypeSizedness { - /// type of definite size (pointers are C-compatible) - Definite, - /// unsized type because it includes an opaque/foreign type (pointers are C-compatible) - UnsizedWithExternType, - /// unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible) - UnsizedWithMetadata, - /// not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like) - NotYetKnown, -} - -/// what type indirection points to a given type -#[derive(Clone, Copy)] -enum IndirectionType { - /// 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, -} - -/// Is this type unsized because it contains (or is) a foreign type? -/// (Returns Err if the type happens to be sized after all) -fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { - let tcx = cx.tcx; - - // note that sizedness is unrelated to inhabitedness - if ty.is_sized(tcx, cx.typing_env()) { - TypeSizedness::Definite - } else { - // the overall type is !Sized or ?Sized - match ty.kind() { - ty::Slice(_) => TypeSizedness::UnsizedWithMetadata, - ty::Str => TypeSizedness::UnsizedWithMetadata, - ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata, - ty::Foreign(..) => TypeSizedness::UnsizedWithExternType, - ty::Adt(def, args) => { - // for now assume: boxes and phantoms don't mess with this - match def.adt_kind() { - AdtKind::Union | AdtKind::Enum => { - bug!("unions and enums are necessarily sized") - } - AdtKind::Struct => { - if let Some(sym::cstring_type | sym::cstr_type) = - tcx.get_diagnostic_name(def.did()) - { - return TypeSizedness::UnsizedWithMetadata; - } - - // FIXME: double-check: non-exhaustive structs from other crates are assumed to be ?Sized, right? - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); - if is_non_exhaustive && !def.did().is_local() { - return TypeSizedness::NotYetKnown; - } - - 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 n_fields = variant.fields.len(); - let last_field = &variant.fields[(n_fields - 1).into()]; - let field_ty = last_field.ty(cx.tcx, args); - let field_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), field_ty) - .unwrap_or(field_ty); - match get_type_sizedness(cx, field_ty) { - s @ (TypeSizedness::UnsizedWithMetadata - | TypeSizedness::UnsizedWithExternType - | TypeSizedness::NotYetKnown) => s, - TypeSizedness::Definite => { - bug!("failed to find the reason why struct `{:?}` is unsized", ty) - } - } - } - } - } - ty::Tuple(tuple) => { - // only the last field may be !Sized (or ?Sized in the case of type params) - let n_fields = tuple.len(); - let field_ty: Ty<'tcx> = tuple[n_fields - 1]; - //let field_ty = last_field.ty(cx.tcx, args); - let field_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), field_ty) - .unwrap_or(field_ty); - match get_type_sizedness(cx, field_ty) { - s @ (TypeSizedness::UnsizedWithMetadata - | TypeSizedness::UnsizedWithExternType - | TypeSizedness::NotYetKnown) => s, - TypeSizedness::Definite => { - bug!("failed to find the reason why tuple `{:?}` is unsized", ty) - } - } - } - - ty_kind @ (ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Float(_) - | ty::Array(..) - | ty::RawPtr(..) - | ty::Ref(..) - | ty::FnPtr(..) - | ty::Never - | ty::Pat(..) // these are (for now) numeric types with a range-based restriction - ) => { - // those types are all sized, right? - bug!( - "This ty_kind (`{:?}`) should be sized, yet we are in a branch of code that deals with unsized types.", - ty_kind, - ) - } - - // While opaque types are checked for earlier, if a projection in a struct field - // normalizes to an opaque type, then it will reach ty::Alias(ty::Opaque) here. - ty::Param(..) | ty::Alias(ty::Opaque | ty::Projection | ty::Inherent, ..) => { - return TypeSizedness::NotYetKnown; - } - - ty::Alias(ty::Weak, ..) - | 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), - } - } -} - pub(crate) fn nonnull_optimization_guaranteed<'tcx>( tcx: TyCtxt<'tcx>, def: ty::AdtDef<'tcx>, @@ -1160,889 +981,6 @@ pub(crate) fn repr_nullable_ptr<'tcx>( None } -impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - /// Check if the type is array and emit an unsafe type lint. - fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { - if let ty::Array(..) = ty.kind() { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - note: fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - inner_ty: None, - span_note: None, - }]); - true - } else { - false - } - } - - /// Checks if the given field's type is "ffi-safe". - fn check_field_type_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, - ) -> 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.check_type_for_ffi(acc, field_ty) - } - - /// Checks if the given `VariantDef`'s field types are "ffi-safe". - fn check_variant_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, - ty: Ty<'tcx>, - def: ty::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) = 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(acc, field, args) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - - false - } else { - // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all - // `PhantomData`). - true - } - } else { - false - }; - - // 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(acc, field, args) { - FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, - FfiPhantom(..) => true, - r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, - } - } - - if all_phantom { - FfiPhantom(ty) - } else if transparent_with_all_zst_fields { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } - } else { - FfiSafe - } - } - - /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" - fn check_indirection_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, - ty: Ty<'tcx>, - inner_ty: Ty<'tcx>, - indirection_type: IndirectionType, - ) -> FfiResult<'tcx> { - use FfiResult::*; - let tcx = self.cx.tcx; - match get_type_sizedness(self.cx, inner_ty) { - TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { - // there's a nuance on what this lint should do for - // function definitions (`extern "C" fn fn_name(...) {...}`) - // versus declarations (`unsafe extern "C" {fn fn_name(...);}`). - // This is touched upon in https://github.com/rust-lang/rust/issues/66220 - // and https://github.com/rust-lang/rust/pull/72700 - // - // The big question is: what does "ABI safety" mean? if you have something translated to a C pointer - // (which has a stable layout) but points to FFI-unsafe type, is it safe? - // On one hand, the function's ABI will match that of a similar C-declared function API, - // on the other, dereferencing the pointer on the other side of the FFI boundary will be painful. - // In this code, the opinion on is split between function declarations and function definitions, - // with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type. - // For declarations, we see this as unsafe, but for definitions, we see this as safe. - // - // For extern function declarations, the actual definition of the function is written somewhere else, - // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) - // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, - // and having the full type information is necessary to compile the function. - if matches!(self.mode, CItemKind::Definition) { - return FfiSafe; - } else { - let inner_res = self.check_type_for_ffi(acc, inner_ty); - return match inner_res { - FfiSafe => inner_res, - _ => FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, - wrapped: Box::new(inner_res), - help: None, - }, - }; - } - } - 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 - // (FIXME note: function 'declarations' there should be treated as definitions) - // - 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) - // - // FIXME: on a side note, we should separate 'true' declarations (non-rust code), - // 'fake' declarations (in traits, needed to be implemented elsewhere), and definitions. - // (for instance, definitions should worry about &self with Self:?Sized, but fake declarations shouldn't) - - // wether 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. - return self.check_type_for_ffi(acc, inner_ty); - } - TypeSizedness::UnsizedWithMetadata => { - let help = match inner_ty.kind() { - ty::Str => Some(fluent::lint_improper_ctypes_str_help), - ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), - ty::Adt(def, _) - if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) - && matches!( - tcx.get_diagnostic_name(def.did()), - Some(sym::cstring_type | sym::cstr_type) - ) - && !acc.base_ty.is_mutable_ptr() => - { - Some(fluent::lint_improper_ctypes_cstr_help) - } - _ => None, - }; - let reason = match indirection_type { - IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, - IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, - IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, - }; - FfiUnsafe { ty, reason, help } - } - } - } - - /// Checks if the given type is "ffi-safe" (has a stable, well-defined - /// representation which can be exported to C code). - fn check_type_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { - use FfiResult::*; - - let tcx = self.cx.tcx; - - // Protect against infinite recursion, for example - // `struct S(*mut S);`. - // FIXME: A recursion limit is necessary as well, for irregular - // recursive types. - if !acc.cache.insert(ty) { - return FfiSafe; - } - - match *ty.kind() { - ty::Adt(def, args) => { - if let Some(inner_ty) = ty.boxed_ty() { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Box); - } - if def.is_phantom_data() { - return FfiPhantom(ty); - } - match def.adt_kind() { - AdtKind::Struct | AdtKind::Union => { - if let Some(sym::cstring_type | sym::cstr_type) = - tcx.get_diagnostic_name(def.did()) - && !acc.base_ty.is_mutable_ptr() - { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_cstr_reason, - help: Some(fluent::lint_improper_ctypes_cstr_help), - }; - } - - if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { - ty, - 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) - }, - }; - } - - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); - if is_non_exhaustive && !def.did().is_local() { - 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(acc, 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, self.mode) - { - return self.check_type_for_ffi(acc, ty); - } - - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; - } - - use improper_ctypes::{ - check_non_exhaustive_variant, non_local_and_non_exhaustive, - }; - - let non_local_def = non_local_and_non_exhaustive(def); - // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_local_def, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; - - match self.check_variant_for_ffi(acc, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } - }); - if let ControlFlow::Break(result) = ret { - return result; - } - - FfiSafe - } - } - } - - ty::Char => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_char_reason, - help: Some(fluent::lint_improper_ctypes_char_help), - }, - - ty::Pat(..) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_pat_reason, - help: Some(fluent::lint_improper_ctypes_pat_help), - }, - - // FIXME: this should probably be architecture-dependant - // same with some ty::Float variants. - ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None } - } - - // Primitive types with a stable representation. - ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, - - ty::Slice(_) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_slice_reason, - help: Some(fluent::lint_improper_ctypes_slice_help), - }, - - ty::Dynamic(..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None } - } - - ty::Str => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_str_reason, - help: Some(fluent::lint_improper_ctypes_str_help), - }, - - ty::Tuple(..) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - }, - - ty::RawPtr(ty, _) - if match ty.kind() { - ty::Tuple(tuple) => tuple.is_empty(), - _ => false, - } => - { - FfiSafe - } - - ty::RawPtr(inner_ty, _) => { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::RawPtr); - } - ty::Ref(_, inner_ty, _) => { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Ref); - } - - // having arrays as arguments / return values themselves is not FFI safe, - // but that is checked elsewhere - // if we reach this, we can assume the array is inside a struct, behind an indirection, etc. - ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), - - ty::FnPtr(sig_tys, hdr) => { - let sig = sig_tys.with(hdr); - if self.is_internal_abi(sig.abi()) { - return FfiUnsafe { - 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.check_type_for_ffi(acc, *arg) { - FfiSafe => {} - r => { - return FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }; - } - } - } - - let ret_ty = sig.output(); - if ret_ty.is_unit() { - return FfiSafe; - } - - match self.check_type_for_ffi(acc, ret_ty) { - r @ (FfiSafe | FfiPhantom(_)) => r, - r => FfiUnsafeWrapper { - ty: ty.clone(), - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }, - } - } - - 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::Alias(ty::Opaque, ..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None } - } - - // `extern "C" fn` functions 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 matches!(self.mode, CItemKind::Definition) => - { - FfiSafe - } - - ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), - - ty::Param(..) - | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) - | 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), - } - } - - fn emit_ffi_unsafe_type_lint( - &mut self, - ty: Ty<'tcx>, - sp: Span, - mut reasons: Vec>, - ) { - let lint = match self.mode { - CItemKind::Declaration => IMPROPER_CTYPES, - CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, - }; - let desc = match self.mode { - CItemKind::Declaration => "block", - CItemKind::Definition => "fn", - }; - for reason in reasons.iter_mut() { - reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() - && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) - { - Some(sp) - } else { - None - }; - } - - self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); - } - - fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { - struct ProhibitOpaqueTypes; - impl<'tcx> ty::visit::TypeVisitor> for ProhibitOpaqueTypes { - type Result = ControlFlow>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - if !ty.has_opaque_types() { - return ControlFlow::Continue(()); - } - - if let ty::Alias(ty::Opaque, ..) = ty.kind() { - ControlFlow::Break(ty) - } else { - ty.super_visit_with(self) - } - } - } - - if let Some(ty) = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), ty) - .unwrap_or(ty) - .visit_with(&mut ProhibitOpaqueTypes) - .break_value() - { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - note: fluent::lint_improper_ctypes_opaque, - span_note: Some(sp), - help: None, - inner_ty: None, - }]); - true - } else { - false - } - } - - fn check_type_for_ffi_and_report_errors( - &mut self, - sp: Span, - ty: Ty<'tcx>, - is_static: bool, - is_return_type: bool, - ) { - if self.check_for_opaque_ty(sp, ty) { - // We've already emitted an error due to an opaque type. - return; - } - - 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 !is_static && self.check_for_array_ty(sp, ty) { - return; - } - - // 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 is_return_type && ty.is_unit() { - return; - } - - let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty }; - match self.check_type_for_ffi(&mut acc, ty) { - FfiResult::FfiSafe => {} - FfiResult::FfiPhantom(ty) => { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - note: fluent::lint_improper_ctypes_only_phantomdata, - span_note: None, // filled later - help: None, - inner_ty: None, - }]); - } - FfiResult::FfiUnsafe { ty, reason, help } => { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - help, - note: reason, - span_note: None, // filled later - inner_ty: None, - }]); - } - ffir @ FfiResult::FfiUnsafeWrapper { .. } => { - let mut ffiresult_recursor = ControlFlow::Continue(&ffir); - let mut cimproper_layers: Vec> = vec![]; - - // this whole while block converts the arbitrarily-deep - // FfiResult stack to an ImproperCTypesLayer Vec - while let ControlFlow::Continue(ref ffir_rec) = ffiresult_recursor { - match ffir_rec { - FfiResult::FfiPhantom(ty) => { - 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: None, - note: fluent::lint_improper_ctypes_only_phantomdata, - span_note: None, // filled later - }); - ffiresult_recursor = ControlFlow::Break(()); - } - FfiResult::FfiUnsafe { ty, reason, help } - | FfiResult::FfiUnsafeWrapper { ty, reason, help, .. } => { - 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: reason.clone(), - span_note: None, // filled later - }); - - if let FfiResult::FfiUnsafeWrapper { wrapped, .. } = ffir_rec { - ffiresult_recursor = ControlFlow::Continue(wrapped.as_ref()); - } else { - ffiresult_recursor = ControlFlow::Break(()); - } - } - FfiResult::FfiSafe => { - bug!("malformed FfiResult stack: it should be unsafe all the way down") - } - }; - } - // should always have at least one type - let last_ty = cimproper_layers.last().unwrap().ty.clone(); - self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers); - } - } - } - - /// Check if a function's argument types and result type are "ffi-safe". - /// - /// For a external ABI function, argument types and the result type are walked to find fn-ptr - /// types that have external ABIs, as these still need checked. - fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); - let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); - - for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { - self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false); - } - } - - if let hir::FnRetTy::Return(ret_hir) = decl.output { - for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) { - self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true); - } - } - } - - /// Check if a function's argument types and result type are "ffi-safe". - fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); - let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); - - for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false); - } - - if let hir::FnRetTy::Return(ret_hir) = decl.output { - self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true); - } - } - - fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { - let ty = self.cx.tcx.type_of(id).instantiate_identity(); - self.check_type_for_ffi_and_report_errors(span, ty, true, false); - } - - fn is_internal_abi(&self, abi: ExternAbi) -> bool { - matches!( - abi, - ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic - ) - } - - /// Find any fn-ptr types with external ABIs in `ty`. - /// - /// For example, `Option` returns `extern "C" fn()` - fn find_fn_ptr_ty_with_external_abi( - &self, - hir_ty: &hir::Ty<'tcx>, - ty: Ty<'tcx>, - ) -> Vec<(Ty<'tcx>, Span)> { - struct FnPtrFinder<'a, 'b, 'tcx> { - visitor: &'a ImproperCTypesVisitor<'b, 'tcx>, - spans: Vec, - tys: Vec>, - } - - impl<'a, 'b, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'a, 'b, 'tcx> { - fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { - debug!(?ty); - if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind - && !self.visitor.is_internal_abi(*abi) - { - self.spans.push(ty.span); - } - - hir::intravisit::walk_ty(self, ty) - } - } - - impl<'a, 'b, 'tcx> ty::visit::TypeVisitor> for FnPtrFinder<'a, 'b, 'tcx> { - type Result = ControlFlow>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - if let ty::FnPtr(_, hdr) = ty.kind() - && !self.visitor.is_internal_abi(hdr.abi) - { - self.tys.push(ty); - } - - ty.super_visit_with(self) - } - } - - let mut visitor = FnPtrFinder { visitor: self, spans: Vec::new(), tys: Vec::new() }; - ty.visit_with(&mut visitor); - visitor.visit_ty_unambig(hir_ty); - - iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect() - } -} - -impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { - fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; - let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); - - match it.kind { - hir::ForeignItemKind::Fn(sig, _, _) => { - if vis.is_internal_abi(abi) { - vis.check_fn(it.owner_id.def_id, sig.decl) - } else { - vis.check_foreign_fn(it.owner_id.def_id, sig.decl); - } - } - hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => { - vis.check_foreign_static(it.owner_id, ty.span); - } - hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), - } - } -} - -impl ImproperCTypesDefinitions { - fn check_ty_maybe_containing_foreign_fnptr<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - hir_ty: &'tcx hir::Ty<'_>, - ty: Ty<'tcx>, - ) { - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; - for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) { - vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false); - } - } - - fn check_arg_for_power_alignment<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - ) -> bool { - // 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 - // - the first field of the struct is an aggregate whose - // recursively first field is a floating-point type greater than - // 4 bytes. - if cx.tcx.sess.target.os != "aix" { - return false; - } - if ty.is_floating_point() && ty.primitive_size(cx.tcx).bytes() > 4 { - return true; - } else if let Adt(adt_def, _) = ty.kind() - && adt_def.is_struct() - { - let struct_variant = adt_def.variant(VariantIdx::ZERO); - // Within a nested struct, all fields are examined to correctly - // report if any fields after the nested struct within the - // original struct are misaligned. - for struct_field in &struct_variant.fields { - let field_ty = cx.tcx.type_of(struct_field.did).instantiate_identity(); - if self.check_arg_for_power_alignment(cx, field_ty) { - return true; - } - } - } - return false; - } - - fn check_struct_for_power_alignment<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - item: &'tcx hir::Item<'tcx>, - ) { - let adt_def = cx.tcx.adt_def(item.owner_id.to_def_id()); - if adt_def.repr().c() - && !adt_def.repr().packed() - && cx.tcx.sess.target.os == "aix" - && !adt_def.all_fields().next().is_none() - { - let struct_variant_data = item.expect_struct().0; - for (index, ..) in struct_variant_data.fields().iter().enumerate() { - // 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. - if index != 0 { - let first_field_def = struct_variant_data.fields()[index]; - let def_id = first_field_def.def_id; - let ty = cx.tcx.type_of(def_id).instantiate_identity(); - if self.check_arg_for_power_alignment(cx, ty) { - cx.emit_span_lint( - USES_POWER_ALIGNMENT, - first_field_def.span, - UsesPowerAlignment, - ); - } - } - } - } - } -} - -/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in -/// `extern "C" { }` blocks): -/// -/// - `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. -impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { - match item.kind { - hir::ItemKind::Static(ty, ..) - | hir::ItemKind::Const(ty, ..) - | hir::ItemKind::TyAlias(ty, ..) => { - self.check_ty_maybe_containing_foreign_fnptr( - cx, - ty, - cx.tcx.type_of(item.owner_id).instantiate_identity(), - ); - } - // See `check_fn`.. - hir::ItemKind::Fn { .. } => {} - // Structs are checked based on if they follow the power alignment - // rule (under repr(C)). - hir::ItemKind::Struct(..) => { - self.check_struct_for_power_alignment(cx, item); - } - // See `check_field_def`.. - hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} - // Doesn't define something that can contain a external type to be checked. - hir::ItemKind::Impl(..) - | hir::ItemKind::TraitAlias(..) - | hir::ItemKind::Trait(..) - | hir::ItemKind::GlobalAsm(..) - | hir::ItemKind::ForeignMod { .. } - | hir::ItemKind::Mod(..) - | hir::ItemKind::Macro(..) - | hir::ItemKind::Use(..) - | hir::ItemKind::ExternCrate(..) => {} - } - } - - fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { - self.check_ty_maybe_containing_foreign_fnptr( - cx, - field.ty, - cx.tcx.type_of(field.def_id).instantiate_identity(), - ); - } - - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: hir::intravisit::FnKind<'tcx>, - decl: &'tcx hir::FnDecl<'_>, - _: &'tcx hir::Body<'_>, - _: Span, - id: LocalDefId, - ) { - use hir::intravisit::FnKind; - - let abi = match kind { - FnKind::ItemFn(_, _, header, ..) => header.abi, - FnKind::Method(_, sig, ..) => sig.header.abi, - _ => return, - }; - - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; - if vis.is_internal_abi(abi) { - vis.check_fn(id, decl); - } else { - vis.check_foreign_fn(id, decl); - } - } -} - declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 1030101c545da..9e318b207f299 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -1,10 +1,28 @@ +use std::iter; use std::ops::ControlFlow; +use rustc_abi::{ExternAbi, VariantIdx}; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::DiagMessage; +use rustc_hir as hir; +use rustc_hir::AmbigArg; use rustc_hir::def::CtorKind; -use rustc_middle::ty; +use rustc_hir::intravisit::VisitorExt; +use rustc_middle::bug; +use rustc_middle::ty::{ + self, Adt, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, +}; +use rustc_span::def_id::LocalDefId; +use rustc_span::{Span, sym}; +use tracing::debug; -use crate::fluent_generated as fluent; +use super::{ + CItemKind, IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, ImproperCTypesDeclarations, + ImproperCTypesDefinitions, USES_POWER_ALIGNMENT, repr_nullable_ptr, +}; +use crate::lints::{ImproperCTypes, ImproperCTypesLayer, UsesPowerAlignment}; +use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; /// Check a variant of a non-exhaustive enum for improper ctypes /// @@ -49,3 +67,1056 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { pub(crate) fn non_local_and_non_exhaustive(def: ty::AdtDef<'_>) -> bool { def.is_variant_list_non_exhaustive() && !def.did().is_local() } + +struct ImproperCTypesVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + mode: CItemKind, +} + +/// Accumulator for recursive ffi type checking +struct CTypesVisitorState<'tcx> { + cache: FxHashSet>, + /// The original type being checked, before we recursed + /// to any other types it contains. + base_ty: Ty<'tcx>, +} + +enum FfiResult<'tcx> { + FfiSafe, + FfiPhantom(Ty<'tcx>), + FfiUnsafe { + ty: Ty<'tcx>, + reason: DiagMessage, + help: Option, + }, + FfiUnsafeWrapper { + ty: Ty<'tcx>, + reason: DiagMessage, + help: Option, + wrapped: Box>, + }, +} + +/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it +#[derive(Clone, Copy)] +enum TypeSizedness { + /// type of definite size (pointers are C-compatible) + Definite, + /// unsized type because it includes an opaque/foreign type (pointers are C-compatible) + UnsizedWithExternType, + /// unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible) + UnsizedWithMetadata, + /// not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like) + NotYetKnown, +} + +/// what type indirection points to a given type +#[derive(Clone, Copy)] +enum IndirectionType { + /// 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, +} + +/// Is this type unsized because it contains (or is) a foreign type? +/// (Returns Err if the type happens to be sized after all) +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()) { + //let is_inh = ty.is_privately_uninhabited(tcx, cx.typing_env()); + TypeSizedness::Definite + } else { + // the overall type is !Sized or ?Sized + match ty.kind() { + ty::Slice(_) => TypeSizedness::UnsizedWithMetadata, + ty::Str => TypeSizedness::UnsizedWithMetadata, + ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata, + ty::Foreign(..) => TypeSizedness::UnsizedWithExternType, + ty::Adt(def, args) => { + // for now assume: boxes and phantoms don't mess with this + match def.adt_kind() { + AdtKind::Union | AdtKind::Enum => { + bug!("unions and enums are necessarily sized") + } + AdtKind::Struct => { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + { + return TypeSizedness::UnsizedWithMetadata; + } + + // FIXME: double-check: non-exhaustive structs from other crates are assumed to be ?Sized, right? + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did().is_local() { + return TypeSizedness::NotYetKnown; + } + + 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 n_fields = variant.fields.len(); + let last_field = &variant.fields[(n_fields - 1).into()]; + let field_ty = last_field.ty(cx.tcx, args); + let field_ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), field_ty) + .unwrap_or(field_ty); + match get_type_sizedness(cx, field_ty) { + s @ (TypeSizedness::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Definite => { + bug!("failed to find the reason why struct `{:?}` is unsized", ty) + } + } + } + } + } + ty::Tuple(tuple) => { + // only the last field may be !Sized (or ?Sized in the case of type params) + let n_fields = tuple.len(); + let field_ty: Ty<'tcx> = tuple[n_fields - 1]; + //let field_ty = last_field.ty(cx.tcx, args); + let field_ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), field_ty) + .unwrap_or(field_ty); + match get_type_sizedness(cx, field_ty) { + s @ (TypeSizedness::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Definite => { + bug!("failed to find the reason why tuple `{:?}` is unsized", ty) + } + } + } + + ty_kind @ (ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Array(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) + | ty::Never + | ty::Pat(..) // these are (for now) numeric types with a range-based restriction + ) => { + // those types are all sized, right? + bug!( + "This ty_kind (`{:?}`) should be sized, yet we are in a branch of code that deals with unsized types.", + ty_kind, + ) + } + + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach ty::Alias(ty::Opaque) here. + ty::Param(..) | ty::Alias(ty::Opaque | ty::Projection | ty::Inherent, ..) => { + return TypeSizedness::NotYetKnown; + } + + ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), + + ty::Alias(ty::Weak, ..) + | 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), + } + } +} + +impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { + /// Check if the type is array and emit an unsafe type lint. + fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + if let ty::Array(..) = ty.kind() { + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_array_reason, + help: Some(fluent::lint_improper_ctypes_array_help), + inner_ty: None, + span_note: None, + }]); + true + } else { + false + } + } + + /// Checks if the given field's type is "ffi-safe". + fn check_field_type_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + field: &ty::FieldDef, + args: GenericArgsRef<'tcx>, + ) -> 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.check_type_for_ffi(acc, field_ty) + } + + /// Checks if the given `VariantDef`'s field types are "ffi-safe". + fn check_variant_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + def: ty::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(acc, field, args) { + FfiUnsafe { ty, .. } if ty.is_unit() => (), + r => return r, + } + + false + } else { + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + true + } + } else { + false + }; + + // 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(acc, field, args) { + FfiSafe => false, + // `()` fields are FFI-safe! + FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, + FfiPhantom(..) => true, + r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, + } + } + + if all_phantom { + FfiPhantom(ty) + } else if transparent_with_all_zst_fields { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + } else { + FfiSafe + } + } + + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" + fn check_indirection_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + inner_ty: Ty<'tcx>, + indirection_type: IndirectionType, + ) -> FfiResult<'tcx> { + use FfiResult::*; + let tcx = self.cx.tcx; + match get_type_sizedness(self.cx, inner_ty) { + TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { + // there's a nuance on what this lint should do for + // function definitions (`extern "C" fn fn_name(...) {...}`) + // versus declarations (`unsafe extern "C" {fn fn_name(...);}`). + // This is touched upon in https://github.com/rust-lang/rust/issues/66220 + // and https://github.com/rust-lang/rust/pull/72700 + // + // The big question is: what does "ABI safety" mean? if you have something translated to a C pointer + // (which has a stable layout) but points to FFI-unsafe type, is it safe? + // On one hand, the function's ABI will match that of a similar C-declared function API, + // on the other, dereferencing the pointer on the other side of the FFI boundary will be painful. + // In this code, the opinion on is split between function declarations and function definitions, + // with the idea that at least one side of the FFI boundary needs to treat the pointee as an opaque type. + // For declarations, we see this as unsafe, but for definitions, we see this as safe. + // + // For extern function declarations, the actual definition of the function is written somewhere else, + // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) + // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, + // and having the full type information is necessary to compile the function. + if matches!(self.mode, CItemKind::Definition) { + return FfiSafe; + } else { + let inner_res = self.check_type_for_ffi(acc, inner_ty); + return match inner_res { + FfiSafe => inner_res, + _ => FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, + wrapped: Box::new(inner_res), + help: None, + }, + }; + } + } + 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 + // (FIXME note: function 'declarations' there should be treated as definitions) + // - 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) + // + // FIXME: on a side note, we should separate 'true' declarations (non-rust code), + // 'fake' declarations (in traits, needed to be implemented elsewhere), and definitions. + // (for instance, definitions should worry about &self with Self:?Sized, but fake declarations shouldn't) + + // wether 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. + return self.check_type_for_ffi(acc, inner_ty); + } + TypeSizedness::UnsizedWithMetadata => { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + ty::Adt(def, _) + if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) + && matches!( + tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + && !acc.base_ty.is_mutable_ptr() => + { + Some(fluent::lint_improper_ctypes_cstr_help) + } + _ => None, + }; + let reason = match indirection_type { + IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, + }; + FfiUnsafe { ty, reason, help } + } + } + } + + /// Checks if the given type is "ffi-safe" (has a stable, well-defined + /// representation which can be exported to C code). + fn check_type_for_ffi( + &self, + acc: &mut CTypesVisitorState<'tcx>, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { + use FfiResult::*; + + let tcx = self.cx.tcx; + + // Protect against infinite recursion, for example + // `struct S(*mut S);`. + // FIXME: A recursion limit is necessary as well, for irregular + // recursive types. + if !acc.cache.insert(ty) { + return FfiSafe; + } + + match *ty.kind() { + ty::Adt(def, args) => { + if let Some(inner_ty) = ty.boxed_ty() { + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Box); + } + if def.is_phantom_data() { + return FfiPhantom(ty); + } + match def.adt_kind() { + AdtKind::Struct | AdtKind::Union => { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + && !acc.base_ty.is_mutable_ptr() + { + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_cstr_reason, + help: Some(fluent::lint_improper_ctypes_cstr_help), + }; + } + + if !def.repr().c() && !def.repr().transparent() { + return FfiUnsafe { + ty, + 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) + }, + }; + } + + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did().is_local() { + 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(acc, 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, self.mode) + { + return self.check_type_for_ffi(acc, ty); + } + + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_enum_repr_reason, + help: Some(fluent::lint_improper_ctypes_enum_repr_help), + }; + } + + let non_local_def = non_local_and_non_exhaustive(def); + // Check the contained variants. + let ret = def.variants().iter().try_for_each(|variant| { + check_non_exhaustive_variant(non_local_def, variant) + .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + + match self.check_variant_for_ffi(acc, ty, def, variant, args) { + FfiSafe => ControlFlow::Continue(()), + r => ControlFlow::Break(r), + } + }); + if let ControlFlow::Break(result) = ret { + return result; + } + + FfiSafe + } + } + } + + ty::Char => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_char_reason, + help: Some(fluent::lint_improper_ctypes_char_help), + }, + + ty::Pat(..) => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_pat_reason, + help: Some(fluent::lint_improper_ctypes_pat_help), + }, + + // FIXME: this should probably be architecture-dependant + // same with some ty::Float variants. + ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None } + } + + // Primitive types with a stable representation. + ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, + + ty::Slice(_) => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_slice_reason, + help: Some(fluent::lint_improper_ctypes_slice_help), + }, + + ty::Dynamic(..) => { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None } + } + + ty::Str => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_str_reason, + help: Some(fluent::lint_improper_ctypes_str_help), + }, + + ty::Tuple(..) => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_tuple_reason, + help: Some(fluent::lint_improper_ctypes_tuple_help), + }, + + ty::RawPtr(ty, _) + if match ty.kind() { + ty::Tuple(tuple) => tuple.is_empty(), + _ => false, + } => + { + FfiSafe + } + + ty::RawPtr(inner_ty, _) => { + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::RawPtr); + } + ty::Ref(_, inner_ty, _) => { + return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Ref); + } + + // having arrays as arguments / return values themselves is not FFI safe, + // but that is checked elsewhere + // if we reach this, we can assume the array is inside a struct, behind an indirection, etc. + ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), + + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(hdr); + if self.is_internal_abi(sig.abi()) { + return FfiUnsafe { + 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.check_type_for_ffi(acc, *arg) { + FfiSafe => {} + r => { + return FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }; + } + } + } + + let ret_ty = sig.output(); + if ret_ty.is_unit() { + return FfiSafe; + } + + match self.check_type_for_ffi(acc, ret_ty) { + r @ (FfiSafe | FfiPhantom(_)) => r, + r => FfiUnsafeWrapper { + ty: ty.clone(), + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }, + } + } + + 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::Alias(ty::Opaque, ..) => { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None } + } + + // `extern "C" fn` functions 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 matches!(self.mode, CItemKind::Definition) => + { + FfiSafe + } + + ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), + + ty::Param(..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) + | 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), + } + } + + fn emit_ffi_unsafe_type_lint( + &mut self, + ty: Ty<'tcx>, + sp: Span, + mut reasons: Vec>, + ) { + let lint = match self.mode { + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + }; + let desc = match self.mode { + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", + }; + for reason in reasons.iter_mut() { + reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() + && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) + { + Some(sp) + } else { + None + }; + } + + self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); + } + + fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + struct ProhibitOpaqueTypes; + impl<'tcx> ty::visit::TypeVisitor> for ProhibitOpaqueTypes { + type Result = ControlFlow>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + if !ty.has_opaque_types() { + return ControlFlow::Continue(()); + } + + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + ControlFlow::Break(ty) + } else { + ty.super_visit_with(self) + } + } + } + + if let Some(ty) = self + .cx + .tcx + .try_normalize_erasing_regions(self.cx.typing_env(), ty) + .unwrap_or(ty) + .visit_with(&mut ProhibitOpaqueTypes) + .break_value() + { + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_opaque, + span_note: Some(sp), + help: None, + inner_ty: None, + }]); + true + } else { + false + } + } + + fn check_type_for_ffi_and_report_errors( + &mut self, + sp: Span, + ty: Ty<'tcx>, + is_static: bool, + is_return_type: bool, + ) { + if self.check_for_opaque_ty(sp, ty) { + // We've already emitted an error due to an opaque type. + return; + } + + 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 !is_static && self.check_for_array_ty(sp, ty) { + return; + } + + // 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 is_return_type && ty.is_unit() { + return; + } + + let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty }; + match self.check_type_for_ffi(&mut acc, ty) { + FfiResult::FfiSafe => {} + FfiResult::FfiPhantom(ty) => { + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + help: None, + inner_ty: None, + }]); + } + FfiResult::FfiUnsafe { ty, reason, help } => { + self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + ty, + help, + note: reason, + span_note: None, // filled later + inner_ty: None, + }]); + } + ffir @ FfiResult::FfiUnsafeWrapper { .. } => { + let mut ffiresult_recursor = ControlFlow::Continue(&ffir); + let mut cimproper_layers: Vec> = vec![]; + + // this whole while block converts the arbitrarily-deep + // FfiResult stack to an ImproperCTypesLayer Vec + while let ControlFlow::Continue(ref ffir_rec) = ffiresult_recursor { + match ffir_rec { + FfiResult::FfiPhantom(ty) => { + 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: None, + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + }); + ffiresult_recursor = ControlFlow::Break(()); + } + FfiResult::FfiUnsafe { ty, reason, help } + | FfiResult::FfiUnsafeWrapper { ty, reason, help, .. } => { + 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: reason.clone(), + span_note: None, // filled later + }); + + if let FfiResult::FfiUnsafeWrapper { wrapped, .. } = ffir_rec { + ffiresult_recursor = ControlFlow::Continue(wrapped.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } + } + FfiResult::FfiSafe => { + bug!("malformed FfiResult stack: it should be unsafe all the way down") + } + }; + } + // should always have at least one type + let last_ty = cimproper_layers.last().unwrap().ty.clone(); + self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers); + } + } + } + + /// Check if a function's argument types and result type are "ffi-safe". + /// + /// For a external ABI function, argument types and the result type are walked to find fn-ptr + /// types that have external ABIs, as these still need checked. + fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false); + } + } + + if let hir::FnRetTy::Return(ret_hir) = decl.output { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true); + } + } + } + + /// Check if a function's argument types and result type are "ffi-safe". + fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false); + } + + if let hir::FnRetTy::Return(ret_hir) = decl.output { + self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true); + } + } + + fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { + let ty = self.cx.tcx.type_of(id).instantiate_identity(); + self.check_type_for_ffi_and_report_errors(span, ty, true, false); + } + + fn is_internal_abi(&self, abi: ExternAbi) -> bool { + matches!( + abi, + ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic + ) + } + + /// Find any fn-ptr types with external ABIs in `ty`. + /// + /// For example, `Option` returns `extern "C" fn()` + fn find_fn_ptr_ty_with_external_abi( + &self, + hir_ty: &hir::Ty<'tcx>, + ty: Ty<'tcx>, + ) -> Vec<(Ty<'tcx>, Span)> { + struct FnPtrFinder<'a, 'b, 'tcx> { + visitor: &'a ImproperCTypesVisitor<'b, 'tcx>, + spans: Vec, + tys: Vec>, + } + + impl<'a, 'b, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'a, 'b, 'tcx> { + fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { + debug!(?ty); + if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind + && !self.visitor.is_internal_abi(*abi) + { + self.spans.push(ty.span); + } + + hir::intravisit::walk_ty(self, ty) + } + } + + impl<'a, 'b, 'tcx> ty::visit::TypeVisitor> for FnPtrFinder<'a, 'b, 'tcx> { + type Result = ControlFlow>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + if let ty::FnPtr(_, hdr) = ty.kind() + && !self.visitor.is_internal_abi(hdr.abi) + { + self.tys.push(ty); + } + + ty.super_visit_with(self) + } + } + + let mut visitor = FnPtrFinder { visitor: self, spans: Vec::new(), tys: Vec::new() }; + ty.visit_with(&mut visitor); + visitor.visit_ty_unambig(hir_ty); + + iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect() + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; + let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); + + match it.kind { + hir::ForeignItemKind::Fn(sig, _, _) => { + if vis.is_internal_abi(abi) { + vis.check_fn(it.owner_id.def_id, sig.decl) + } else { + vis.check_foreign_fn(it.owner_id.def_id, sig.decl); + } + } + hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => { + vis.check_foreign_static(it.owner_id, ty.span); + } + hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), + } + } +} + +impl ImproperCTypesDefinitions { + fn check_ty_maybe_containing_foreign_fnptr<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + hir_ty: &'tcx hir::Ty<'_>, + ty: Ty<'tcx>, + ) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) { + vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false); + } + } + + fn check_arg_for_power_alignment<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + ) -> bool { + // 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 + // - the first field of the struct is an aggregate whose + // recursively first field is a floating-point type greater than + // 4 bytes. + if cx.tcx.sess.target.os != "aix" { + return false; + } + if ty.is_floating_point() && ty.primitive_size(cx.tcx).bytes() > 4 { + return true; + } else if let Adt(adt_def, _) = ty.kind() + && adt_def.is_struct() + { + let struct_variant = adt_def.variant(VariantIdx::ZERO); + // Within a nested struct, all fields are examined to correctly + // report if any fields after the nested struct within the + // original struct are misaligned. + for struct_field in &struct_variant.fields { + let field_ty = cx.tcx.type_of(struct_field.did).instantiate_identity(); + if self.check_arg_for_power_alignment(cx, field_ty) { + return true; + } + } + } + return false; + } + + fn check_struct_for_power_alignment<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + item: &'tcx hir::Item<'tcx>, + ) { + let adt_def = cx.tcx.adt_def(item.owner_id.to_def_id()); + if adt_def.repr().c() + && !adt_def.repr().packed() + && cx.tcx.sess.target.os == "aix" + && !adt_def.all_fields().next().is_none() + { + let struct_variant_data = item.expect_struct().0; + for (index, ..) in struct_variant_data.fields().iter().enumerate() { + // 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. + if index != 0 { + let first_field_def = struct_variant_data.fields()[index]; + let def_id = first_field_def.def_id; + let ty = cx.tcx.type_of(def_id).instantiate_identity(); + if self.check_arg_for_power_alignment(cx, ty) { + cx.emit_span_lint( + USES_POWER_ALIGNMENT, + first_field_def.span, + UsesPowerAlignment, + ); + } + } + } + } + } +} + +/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in +/// `extern "C" { }` blocks): +/// +/// - `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. +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + match item.kind { + hir::ItemKind::Static(ty, ..) + | hir::ItemKind::Const(ty, ..) + | hir::ItemKind::TyAlias(ty, ..) => { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + ty, + cx.tcx.type_of(item.owner_id).instantiate_identity(), + ); + } + // See `check_fn`.. + hir::ItemKind::Fn { .. } => {} + // Structs are checked based on if they follow the power alignment + // rule (under repr(C)). + hir::ItemKind::Struct(..) => { + self.check_struct_for_power_alignment(cx, item); + } + // See `check_field_def`.. + hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} + // Doesn't define something that can contain a external type to be checked. + hir::ItemKind::Impl(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::Trait(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Mod(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Use(..) + | hir::ItemKind::ExternCrate(..) => {} + } + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + field.ty, + cx.tcx.type_of(field.def_id).instantiate_identity(), + ); + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: hir::intravisit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + _: &'tcx hir::Body<'_>, + _: Span, + id: LocalDefId, + ) { + use hir::intravisit::FnKind; + + let abi = match kind { + FnKind::ItemFn(_, _, header, ..) => header.abi, + FnKind::Method(_, sig, ..) => sig.header.abi, + _ => return, + }; + + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + if vis.is_internal_abi(abi) { + vis.check_fn(id, decl); + } else { + vis.check_foreign_fn(id, decl); + } + } +} From b3052cd7538f3015354d207117865a41ce84a052 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sun, 5 Jan 2025 11:17:28 +0100 Subject: [PATCH 6/9] lint ImproperCTypes: near-complete rewrite, first version [does not pass tests] --- .../rustc_lint/src/types/improper_ctypes.rs | 1185 ++++++++++------- .../cmse-nonsecure-call/via-registers.rs | 1 + 2 files changed, 692 insertions(+), 494 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9e318b207f299..3648a1a1ba4d2 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -15,6 +15,7 @@ use rustc_middle::ty::{ }; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; +use rustc_type_ir::{Binder,FnSig}; use tracing::debug; use super::{ @@ -24,6 +25,37 @@ use super::{ use crate::lints::{ImproperCTypes, ImproperCTypesLayer, UsesPowerAlignment}; use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; +type Sig<'tcx> = Binder, FnSig>>; + +/// for a given `extern "ABI"`, tell wether that ABI is *not* considered a FFI boundary +fn fn_abi_is_internal(abi: ExternAbi) -> bool { + matches!( + abi, + ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic + ) +} + + + +// a shorthand for an often used lifetime-region normalisation step +#[inline] +fn normalize_if_possible<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx>{ + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) + .unwrap_or(ty) +} + +// 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); + normalize_if_possible(cx, field_ty) +} + + /// Check a variant of a non-exhaustive enum for improper ctypes /// /// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". @@ -70,15 +102,7 @@ pub(crate) fn non_local_and_non_exhaustive(def: ty::AdtDef<'_>) -> bool { struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - mode: CItemKind, -} - -/// Accumulator for recursive ffi type checking -struct CTypesVisitorState<'tcx> { cache: FxHashSet>, - /// The original type being checked, before we recursed - /// to any other types it contains. - base_ty: Ty<'tcx>, } enum FfiResult<'tcx> { @@ -164,13 +188,12 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type let variant = def.non_enum_variant(); // only the last field may be !Sized (or ?Sized in the case of type params) - let n_fields = variant.fields.len(); - let last_field = &variant.fields[(n_fields - 1).into()]; - let field_ty = last_field.ty(cx.tcx, args); - let field_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), field_ty) - .unwrap_or(field_ty); + let last_field = &variant.fields[(&variant.fields.len()-1).into()]; + // let last_field = match &variant.fields.iter().last(){ // TODO performance + // Some(last_field) => last_field, + // None => bug!("Empty struct should be Sized, right?"), // TODO: nonexhaustive empty struct from another crate/module + // }; + let field_ty = get_type_from_field(cx, last_field, args); match get_type_sizedness(cx, field_ty) { s @ (TypeSizedness::UnsizedWithMetadata | TypeSizedness::UnsizedWithExternType @@ -184,14 +207,12 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type } ty::Tuple(tuple) => { // only the last field may be !Sized (or ?Sized in the case of type params) - let n_fields = tuple.len(); - let field_ty: Ty<'tcx> = tuple[n_fields - 1]; - //let field_ty = last_field.ty(cx.tcx, args); - let field_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), field_ty) - .unwrap_or(field_ty); - match get_type_sizedness(cx, field_ty) { + 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 = normalize_if_possible(cx, item_ty); + match get_type_sizedness(cx, item_ty) { s @ (TypeSizedness::UnsizedWithMetadata | TypeSizedness::UnsizedWithExternType | TypeSizedness::NotYetKnown) => s, @@ -242,92 +263,124 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type } } -impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - /// Check if the type is array and emit an unsafe type lint. - fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { - if let ty::Array(..) = ty.kind() { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - note: fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - inner_ty: None, - span_note: None, - }]); - true - } else { - false +#[repr(u8)] +#[derive(Clone,Copy,Debug)] +enum CTypesVisitorState{ + // bitflags: + // 0001: inner type + // 0010: static + // 0100: function return + // 1000: used in declared function + StaticTy = 0b0010, + StaticInner = 0b0011, + ArgumentTyInDefinition = 0b1000, + ReturnTyInDefinition = 0b1100, + ArgumentInnerInDefinition = 0b1001, + ReturnInnerInDefinition = 0b1101, + ArgumentTyInDeclaration = 0b0000, + ReturnTyInDeclaration = 0b0100, + ArgumentInnerInDeclaration = 0b0001, + ReturnInnerInDeclaration = 0b0101, +} + +impl CTypesVisitorState{ + /// wether we are being directly used in a function or static + fn has_direct_use(self) -> bool { + ((self as u8) & 0b0001) == 0 + } + /// wether the type is used (directly or not) in a static variable + fn is_in_static(self) -> bool { + ((self as u8) & 0b0010) != 0 + } + /// wether the type is used (directly or not) in a function, in return position + fn is_in_function_return(self) -> bool { + let ret = ((self as u8) & 0b0100) != 0; + #[cfg(debug_assertions)] + if ret{ + assert!(!self.is_in_static()); } + ret } - - /// Checks if the given field's type is "ffi-safe". - fn check_field_type_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, - ) -> 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.check_type_for_ffi(acc, field_ty) + /// wether the type is used (directly or not) in a defined function + /// in other words, wether 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 { + let ret = ((self as u8) & 0b1000) != 0; + #[cfg(debug_assertions)] + if ret{ + assert!(!self.is_in_static()); + } + ret + } + /// get a new CTypesVisitorState from the current one, to visit the current type's inner types + fn to_inner_ty(self) -> Self{ + //(self as u8 | 0b0001).try_into().unwrap() + match self{ + Self::StaticTy|Self::StaticInner => Self::StaticInner, + Self::ArgumentTyInDefinition|Self::ArgumentInnerInDefinition + => Self::ArgumentInnerInDefinition, + Self::ReturnTyInDefinition|Self::ReturnInnerInDefinition + => Self::ReturnInnerInDefinition, + Self::ArgumentTyInDeclaration|Self::ArgumentInnerInDeclaration + => Self::ArgumentInnerInDeclaration, + Self::ReturnTyInDeclaration|Self::ReturnInnerInDeclaration + => Self::ReturnInnerInDeclaration, + } } +} - /// Checks if the given `VariantDef`'s field types are "ffi-safe". - fn check_variant_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, +impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { + + /// Checks wether an `extern "ABI" fn` function pointer is indeed FFI-safe to call + fn visit_fnptr( + &mut self, + // TODO this feels wrong but rustc doesn't compile withoug that :') + mode: CItemKind, ty: Ty<'tcx>, - def: ty::AdtDef<'tcx>, - variant: &ty::VariantDef, - args: GenericArgsRef<'tcx>, + sig: Sig<'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(acc, field, args) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - - false - } else { - // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all - // `PhantomData`). - true - } - } else { - false + debug_assert!(!fn_abi_is_internal(sig.abi())); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + let state = match mode { + CItemKind::Declaration => CTypesVisitorState::ArgumentTyInDeclaration, + CItemKind::Definition => CTypesVisitorState::ArgumentTyInDefinition, }; - - // 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(acc, field, args) { - FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, - FfiPhantom(..) => true, - r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, + for arg in sig.inputs() { + match self.visit_type(state, *arg) { + FfiSafe => {} + r => { + return FfiUnsafeWrapper { + ty, + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }; + } } } - if all_phantom { - FfiPhantom(ty) - } else if transparent_with_all_zst_fields { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } - } else { - FfiSafe + let ret_ty = sig.output(); + let state = match mode { + CItemKind::Declaration => CTypesVisitorState::ReturnTyInDeclaration, + CItemKind::Definition => CTypesVisitorState::ReturnTyInDefinition, + }; + + match self.visit_type(state, ret_ty) { + r @ (FfiSafe | FfiPhantom(_)) => r, + r @ _ => FfiUnsafeWrapper { + ty: ty.clone(), + reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, + help: None, + wrapped: Box::new(r), + }, } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" - fn check_indirection_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, + fn visit_indirection( + &mut self, + state: CTypesVisitorState, ty: Ty<'tcx>, inner_ty: Ty<'tcx>, indirection_type: IndirectionType, @@ -352,12 +405,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // // For extern function declarations, the actual definition of the function is written somewhere else, // meaning the declaration is free to express this opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void (opaque callee-side) + // (or other possibly better tricks, see https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs) // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, // and having the full type information is necessary to compile the function. - if matches!(self.mode, CItemKind::Definition) { + if state.is_in_defined_function() { return FfiSafe; } else { - let inner_res = self.check_type_for_ffi(acc, inner_ty); + let inner_res = self.visit_type(state.to_inner_ty(), inner_ty); return match inner_res { FfiSafe => inner_res, _ => FfiUnsafeWrapper { @@ -387,7 +441,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // wether 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. - return self.check_type_for_ffi(acc, inner_ty); + return self.visit_type(state.to_inner_ty(), inner_ty); } TypeSizedness::UnsizedWithMetadata => { let help = match inner_ty.kind() { @@ -396,10 +450,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Adt(def, _) if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) && matches!( + // TODO also use that trick to separate closures from dyn, if possible tcx.get_diagnostic_name(def.did()), Some(sym::cstring_type | sym::cstr_type) ) - && !acc.base_ty.is_mutable_ptr() => + // TODO vv here vv : originally used acc.base_ty, needs more generic + && !ty.is_mutable_ptr() => { Some(fluent::lint_improper_ctypes_cstr_help) } @@ -415,11 +471,180 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } + /// Checks if the given `VariantDef`'s field types are "ffi-safe". + fn visit_variant_fields( + &mut self, + state: CTypesVisitorState, + ty: Ty<'tcx>, + def: ty::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.. + let field_ty = get_type_from_field(self.cx, field, args); + match self.visit_type(state.to_inner_ty(), field_ty) { + FfiUnsafe { ty, .. } if ty.is_unit() => (), + r => return r, + } + + false + } else { + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + true + } + } else { + false + }; + + // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. + let mut all_phantom = !variant.fields.is_empty(); + for field in &variant.fields { + let field_ty = get_type_from_field(self.cx, field, args); + all_phantom &= match self.visit_type(state.to_inner_ty(), field_ty) { + FfiSafe => false, + // `()` fields are FFI-safe! + FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, + FfiPhantom(..) => true, + r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, + } + } + + if all_phantom { + FfiPhantom(ty) + } else if transparent_with_all_zst_fields { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + } else { + FfiSafe + } + } + + fn visit_struct_union( + &mut self, + state: CTypesVisitorState, + ty: Ty<'tcx>, + def: ty::AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Struct|AdtKind::Union)); + use FfiResult::*; + + if !def.repr().c() && !def.repr().transparent() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint_improper_ctypes_struct_layout_reason + } else { + fluent::lint_improper_ctypes_union_layout_reason + }, + help: if def.is_struct() { + Some(fluent::lint_improper_ctypes_struct_layout_help) + } else { + Some(fluent::lint_improper_ctypes_union_layout_help) + }, + }; + } + + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did().is_local() { + 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.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) + } + + fn visit_enum( + &mut self, + state: CTypesVisitorState, + ty: Ty<'tcx>, + def: ty::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 implicitely handled as the empty type: + // values for them must never be constructed, + // functions using them as argument or return must... err. + // TODO + 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, + if state.is_in_defined_function(){CItemKind::Definition}else{CItemKind::Declaration}, + ) + { + return self.visit_type(state.to_inner_ty(), ty); + } + + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_enum_repr_reason, + help: Some(fluent::lint_improper_ctypes_enum_repr_help), + }; + } + + let non_local_def = non_local_and_non_exhaustive(def); + // Check the contained variants. + let ret = def.variants().iter().try_for_each(|variant| { + check_non_exhaustive_variant(non_local_def, variant) + .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + + match self.visit_variant_fields(state, ty, def, variant, args) { + // TODO no need to pick only one + FfiSafe => ControlFlow::Continue(()), + r => ControlFlow::Break(r), + } + }); + if let ControlFlow::Break(result) = ret { + return result; + } + + FfiSafe + } + /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn check_type_for_ffi( - &self, - acc: &mut CTypesVisitorState<'tcx>, + fn visit_type( + &mut self, + state: CTypesVisitorState, ty: Ty<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; @@ -430,118 +655,39 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // `struct S(*mut S);`. // FIXME: A recursion limit is necessary as well, for irregular // recursive types. - if !acc.cache.insert(ty) { + if !self.cache.insert(ty) { return FfiSafe; } match *ty.kind() { ty::Adt(def, args) => { if let Some(inner_ty) = ty.boxed_ty() { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Box); + return self.visit_indirection(state, ty, inner_ty, IndirectionType::Box); } if def.is_phantom_data() { return FfiPhantom(ty); } match def.adt_kind() { AdtKind::Struct | AdtKind::Union => { - if let Some(sym::cstring_type | sym::cstr_type) = + // I thought CStr could not be reached here: + // - not using an indirection would cause a compile error prior to this lint + // - and using one would cause the lint to catch on the indirection before reaching its pointee + // but for some reason one can just go and write function *pointers* like that: + // `type Foo = extern "C" fn(::std::ffi::CStr);` + if let Some(sym::cstring_type|sym::cstr_type) = tcx.get_diagnostic_name(def.did()) - && !acc.base_ty.is_mutable_ptr() { return FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_cstr_reason, - help: Some(fluent::lint_improper_ctypes_cstr_help), - }; - } - - if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { - ty, - 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) - }, - }; - } - - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); - if is_non_exhaustive && !def.did().is_local() { - 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) - }, + help: Some(fluent::lint_improper_ctypes_cstr_help), // TODO look into many cases: own/CString, ref/CString, own/struct/CString, ref/struct/CString, own/struct/ref/CString, etc... }; } - self.check_variant_for_ffi(acc, ty, def, def.non_enum_variant(), args) + self.visit_struct_union(state, ty, def, args) } AdtKind::Enum => { - if def.variants().is_empty() { - // Empty enums are okay... although sort of useless. - return FfiSafe; - } - // Check for a repr() attribute to specify the size of the - // discriminant. - if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() - { - // Special-case types like `Option` and `Result` - if let Some(ty) = - repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty, self.mode) - { - return self.check_type_for_ffi(acc, ty); - } - - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; - } - - let non_local_def = non_local_and_non_exhaustive(def); - // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_local_def, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; - - match self.check_variant_for_ffi(acc, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } - }); - if let ControlFlow::Break(result) = ret { - return result; - } - - FfiSafe + self.visit_enum(state, ty, def, args) } } } @@ -583,11 +729,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(fluent::lint_improper_ctypes_str_help), }, - ty::Tuple(..) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - }, + ty::Tuple(tuple) => + // TODO do we move the "unit types allowed as fields" here? assuming they are also allowed behind indirections + // ...doesn't seem so. Existing logic doesn't like Boxes and refs to those. + if tuple.is_empty() && state.is_in_function_return() && state.has_direct_use() { + // C functions can return void + FfiSafe + } else { + FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_tuple_reason, + help: Some(fluent::lint_improper_ctypes_tuple_help), + } + }, ty::RawPtr(ty, _) if match ty.kind() { @@ -599,55 +753,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::RawPtr(inner_ty, _) => { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::RawPtr); + return self.visit_indirection(state, ty, inner_ty, IndirectionType::RawPtr); } ty::Ref(_, inner_ty, _) => { - return self.check_indirection_for_ffi(acc, ty, inner_ty, IndirectionType::Ref); + return self.visit_indirection(state, ty, inner_ty, IndirectionType::Ref); } - // having arrays as arguments / return values themselves is not FFI safe, - // but that is checked elsewhere - // if we reach this, we can assume the array is inside a struct, behind an indirection, etc. - ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty), + ty::Array(inner_ty, _) => { + if state.has_direct_use() && !state.is_in_static() { + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. + FfiUnsafe{ + ty, + reason:fluent::lint_improper_ctypes_array_reason, + help: Some(fluent::lint_improper_ctypes_array_help), + } + } else { + self.visit_type(state.to_inner_ty(), inner_ty) + } + } ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); - if self.is_internal_abi(sig.abi()) { - return FfiUnsafe { + if fn_abi_is_internal(sig.abi()) { + FfiUnsafe { 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.check_type_for_ffi(acc, *arg) { - FfiSafe => {} - r => { - return FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }; - } } - } - - let ret_ty = sig.output(); - if ret_ty.is_unit() { - return FfiSafe; - } - - match self.check_type_for_ffi(acc, ret_ty) { - r @ (FfiSafe | FfiPhantom(_)) => r, - r => FfiUnsafeWrapper { - ty: ty.clone(), - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }, + } else { + let mode = if state.is_in_defined_function() { + CItemKind::Definition + } else { + CItemKind::Declaration + }; + self.visit_fnptr(mode, ty, sig) } } @@ -662,7 +802,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // `extern "C" fn` functions 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 matches!(self.mode, CItemKind::Definition) => + if state.is_in_defined_function() => { FfiSafe } @@ -683,34 +823,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - fn emit_ffi_unsafe_type_lint( - &mut self, - ty: Ty<'tcx>, - sp: Span, - mut reasons: Vec>, - ) { - let lint = match self.mode { - CItemKind::Declaration => IMPROPER_CTYPES, - CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, - }; - let desc = match self.mode { - CItemKind::Declaration => "block", - CItemKind::Definition => "fn", - }; - for reason in reasons.iter_mut() { - reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() - && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) - { - Some(sp) - } else { - None - }; - } - - self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); - } - - fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + fn check_for_opaque_ty(&mut self, ty: Ty<'tcx>) -> FfiResult<'tcx> { struct ProhibitOpaqueTypes; impl<'tcx> ty::visit::TypeVisitor> for ProhibitOpaqueTypes { type Result = ControlFlow>; @@ -728,79 +841,303 @@ 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) + if let Some(ty) = ty .visit_with(&mut ProhibitOpaqueTypes) .break_value() { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { + FfiResult::FfiUnsafe{ ty, - note: fluent::lint_improper_ctypes_opaque, - span_note: Some(sp), + reason: fluent::lint_improper_ctypes_opaque, help: None, - inner_ty: None, - }]); - true + } } else { - false + FfiResult::FfiSafe } } - fn check_type_for_ffi_and_report_errors( + fn check_for_type( &mut self, - sp: Span, + state: CTypesVisitorState, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { + let ty = normalize_if_possible(self.cx, ty); + + match self.check_for_opaque_ty(ty) { + FfiResult::FfiSafe => (), + ffi_res @ _ => return ffi_res, + } + self.visit_type(state, ty) + } + + fn check_for_fnptr( + &mut self, + mode: CItemKind, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { + let ty = normalize_if_possible(self.cx, ty); + + match self.check_for_opaque_ty(ty) { + FfiResult::FfiSafe => (), + ffi_res @ _ => return ffi_res, + } + + match *ty.kind() { + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(hdr); + if fn_abi_is_internal(sig.abi()) { + bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one") + } else { + self.visit_fnptr(mode, ty, sig) + } + }, + _ => bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not whtaever this is"), + } + } +} + +/// common structure for functionality that is shared +/// between ImproperCTypesDeclarations and ImproperCTypesDefinitions +struct ImproperCTypesLint<'c, 'tcx>{ + cx: &'c LateContext<'tcx> +} + +impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ + fn check_arg_for_power_alignment( + &mut self, + ty: Ty<'tcx>, + ) -> bool { + // 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 + // - the first field of the struct is an aggregate whose + // recursively first field is a floating-point type greater than + // 4 bytes. + let tcx = self.cx.tcx; + if tcx.sess.target.os != "aix" { + return false; + } + if ty.is_floating_point() && ty.primitive_size(tcx).bytes() > 4 { + return true; + } else if let Adt(adt_def, _) = ty.kind() + && adt_def.is_struct() + { + let struct_variant = adt_def.variant(VariantIdx::ZERO); + // Within a nested struct, all fields are examined to correctly + // report if any fields after the nested struct within the + // original struct are misaligned. + for struct_field in &struct_variant.fields { + let field_ty = tcx.type_of(struct_field.did).instantiate_identity(); + if self.check_arg_for_power_alignment(field_ty) { + return true; + } + } + } + return false; + } + + fn check_struct_for_power_alignment( + &mut self, + item: &'tcx hir::Item<'tcx>, + ) { + let tcx = self.cx.tcx; + let adt_def = tcx.adt_def(item.owner_id.to_def_id()); + if adt_def.repr().c() + && !adt_def.repr().packed() + && tcx.sess.target.os == "aix" + && !adt_def.all_fields().next().is_none() + { + let struct_variant_data = item.expect_struct().0; + for (index, ..) in struct_variant_data.fields().iter().enumerate() { + // 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. + if index != 0 { + let first_field_def = struct_variant_data.fields()[index]; + let def_id = first_field_def.def_id; + let ty = tcx.type_of(def_id).instantiate_identity(); + if self.check_arg_for_power_alignment(ty) { + self.cx.emit_span_lint( + USES_POWER_ALIGNMENT, + first_field_def.span, + UsesPowerAlignment, + ); + } + } + } + } + } + + /// Find any fn-ptr types with external ABIs in `ty`. + /// + /// For example, `Option` returns `extern "C" fn()` + fn check_type_for_external_abi_fnptr( + &self, + fn_mode: CItemKind, + hir_ty: &hir::Ty<'tcx>, ty: Ty<'tcx>, - is_static: bool, - is_return_type: bool, ) { - if self.check_for_opaque_ty(sp, ty) { - // We've already emitted an error due to an opaque type. - return; + struct FnPtrFinder<'tcx> { + spans: Vec, + tys: Vec>, + } + + impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> { + fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { + debug!(?ty); + if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind + && !fn_abi_is_internal(*abi) + { + self.spans.push(ty.span); + } + + hir::intravisit::walk_ty(self, ty) + } + } + + impl<'tcx> ty::visit::TypeVisitor> for FnPtrFinder<'tcx> { + type Result = ControlFlow>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + if let ty::FnPtr(_, hdr) = ty.kind() + && !fn_abi_is_internal(hdr.abi) + { + self.tys.push(ty); + } + + ty.super_visit_with(self) + } + } + + let mut visitor = FnPtrFinder {spans: Vec::new(), tys: Vec::new() }; + ty.visit_with(&mut visitor); + visitor.visit_ty_unambig(hir_ty); + + let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)); + all_types + .map(|(fn_ptr_ty, span)|{ + // TODO this will probably lead to error deduplication: fix this + let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + let ffi_res = visitor.check_for_fnptr(fn_mode, fn_ptr_ty); + (span, ffi_res) + }) + //.flatten() // TODO already planning for more + // even in function *definitions*, FnPtr:s are always function declarations. so it makes sense ...right? + .map(|(span, ffi_res)|self.process_ffi_result(span, ffi_res, fn_mode)) + .reduce(|_a:(),_b:()|()); + //.drain(); + } + + /// For a function that doesn't need to be "ffi-safe", look for fn-ptr argument/return types + /// that need to be checked for ffi-safety + fn check_fn_for_external_abi_fnptr( + &self, + fn_mode: CItemKind, + def_id: LocalDefId, + decl: &'tcx hir::FnDecl<'_> + ) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + self.check_type_for_external_abi_fnptr(fn_mode, input_hir, *input_ty); + // for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { + // // no CTypesVisitorState needed, it's overwritten as soon as the FnPtr is entered + // // can default to ArgumentTyInDeclaration if needed + // let res = todo!(fn_ptr_ty); + // res.iter().map(|res|self.process_ffi_result(span, res)).drain(); + // } } - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); + if let hir::FnRetTy::Return(ret_hir) = decl.output { + self.check_type_for_external_abi_fnptr(fn_mode, ret_hir, sig.output()); + } + } + - // 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 !is_static && self.check_for_array_ty(sp, ty) { - return; + /// Check that an extern "ABI" static variable is of a ffi-safe type + fn check_foreign_static( + &self, + id: hir::OwnerId, + span: Span + ) { + let ty = self.cx.tcx.type_of(id).instantiate_identity(); + let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + let ffi_res = visitor.check_for_type(CTypesVisitorState::StaticTy, ty); + self.process_ffi_result(span, ffi_res, CItemKind::Declaration); + } + + /// Check if a function's argument types and result type are "ffi-safe". + fn check_foreign_fn( + &self, + fn_mode: CItemKind, + def_id: LocalDefId, + decl: &'tcx hir::FnDecl<'_>, + ) { + + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + let visit_state = match fn_mode{ + CItemKind::Definition => CTypesVisitorState::ArgumentTyInDefinition, + CItemKind::Declaration => CTypesVisitorState::ArgumentTyInDeclaration, + }; + let ffi_res = visitor.check_for_type(visit_state, *input_ty); + self.process_ffi_result(input_hir.span, ffi_res, fn_mode); } - // 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 is_return_type && ty.is_unit() { - return; + if let hir::FnRetTy::Return(ret_hir) = decl.output { + let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + let visit_state = match fn_mode{ + CItemKind::Definition => CTypesVisitorState::ReturnTyInDefinition, + CItemKind::Declaration => CTypesVisitorState::ReturnTyInDeclaration, + }; + let ffi_res = visitor.check_for_type(visit_state, sig.output()); + self.process_ffi_result(ret_hir.span, ffi_res, fn_mode); } + } + - let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty }; - match self.check_type_for_ffi(&mut acc, ty) { + fn process_ffi_result( + &self, + sp: Span, + res: FfiResult<'tcx>, + fn_mode: CItemKind, + ) { + match res { FfiResult::FfiSafe => {} FfiResult::FfiPhantom(ty) => { - self.emit_ffi_unsafe_type_lint(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - note: fluent::lint_improper_ctypes_only_phantomdata, - span_note: None, // filled later - help: None, - inner_ty: None, - }]); + self.emit_ffi_unsafe_type_lint( + ty.clone(), + sp, + 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(ty.clone(), sp, vec![ImproperCTypesLayer { - ty, - help, - note: reason, - span_note: None, // filled later - inner_ty: None, - }]); + self.emit_ffi_unsafe_type_lint( + ty.clone(), + sp, + vec![ImproperCTypesLayer { + ty, + help, + note: reason, + span_note: None, // filled later + inner_ty: None, + }], + fn_mode, + ); } ffir @ FfiResult::FfiUnsafeWrapper { .. } => { let mut ffiresult_recursor = ControlFlow::Continue(&ffir); - let mut cimproper_layers: Vec> = vec![]; + let mut cimproper_layers: Vec> = vec![]; // this whole while block converts the arbitrarily-deep // FfiResult stack to an ImproperCTypesLayer Vec @@ -845,207 +1182,67 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } // should always have at least one type let last_ty = cimproper_layers.last().unwrap().ty.clone(); - self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers); - } - } - } - - /// Check if a function's argument types and result type are "ffi-safe". - /// - /// For a external ABI function, argument types and the result type are walked to find fn-ptr - /// types that have external ABIs, as these still need checked. - fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); - let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); - - for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { - self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false); + self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers, fn_mode); } } - - if let hir::FnRetTy::Return(ret_hir) = decl.output { - for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) { - self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true); - } - } - } - - /// Check if a function's argument types and result type are "ffi-safe". - fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); - let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); - - for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false); - } - - if let hir::FnRetTy::Return(ret_hir) = decl.output { - self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true); - } - } - - fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { - let ty = self.cx.tcx.type_of(id).instantiate_identity(); - self.check_type_for_ffi_and_report_errors(span, ty, true, false); - } - - fn is_internal_abi(&self, abi: ExternAbi) -> bool { - matches!( - abi, - ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic - ) } - /// Find any fn-ptr types with external ABIs in `ty`. - /// - /// For example, `Option` returns `extern "C" fn()` - fn find_fn_ptr_ty_with_external_abi( + fn emit_ffi_unsafe_type_lint( &self, - hir_ty: &hir::Ty<'tcx>, ty: Ty<'tcx>, - ) -> Vec<(Ty<'tcx>, Span)> { - struct FnPtrFinder<'a, 'b, 'tcx> { - visitor: &'a ImproperCTypesVisitor<'b, 'tcx>, - spans: Vec, - tys: Vec>, - } - - impl<'a, 'b, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'a, 'b, 'tcx> { - fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { - debug!(?ty); - if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind - && !self.visitor.is_internal_abi(*abi) - { - self.spans.push(ty.span); - } - - hir::intravisit::walk_ty(self, ty) - } - } - - impl<'a, 'b, 'tcx> ty::visit::TypeVisitor> for FnPtrFinder<'a, 'b, 'tcx> { - type Result = ControlFlow>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - if let ty::FnPtr(_, hdr) = ty.kind() - && !self.visitor.is_internal_abi(hdr.abi) - { - self.tys.push(ty); - } - - ty.super_visit_with(self) - } + sp: Span, + mut reasons: Vec>, + fn_mode: CItemKind, + ) { + let lint = match fn_mode { + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + }; + let desc = match fn_mode { + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", + }; + for reason in reasons.iter_mut() { + reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() + && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) + { + Some(sp) + } else { + None + }; } - let mut visitor = FnPtrFinder { visitor: self, spans: Vec::new(), tys: Vec::new() }; - ty.visit_with(&mut visitor); - visitor.visit_ty_unambig(hir_ty); - - iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect() + self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); } + } impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; + let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); + let lint = ImproperCTypesLint{cx}; match it.kind { hir::ForeignItemKind::Fn(sig, _, _) => { - if vis.is_internal_abi(abi) { - vis.check_fn(it.owner_id.def_id, sig.decl) + if fn_abi_is_internal(abi) { + lint.check_fn_for_external_abi_fnptr( + CItemKind::Declaration, + it.owner_id.def_id, + sig.decl + ) } else { - vis.check_foreign_fn(it.owner_id.def_id, sig.decl); + lint.check_foreign_fn(CItemKind::Declaration, it.owner_id.def_id, sig.decl); } } - hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => { - vis.check_foreign_static(it.owner_id, ty.span); + hir::ForeignItemKind::Static(ty, _, _) if !fn_abi_is_internal(abi) => { + lint.check_foreign_static(it.owner_id, ty.span); } hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), } } } -impl ImproperCTypesDefinitions { - fn check_ty_maybe_containing_foreign_fnptr<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - hir_ty: &'tcx hir::Ty<'_>, - ty: Ty<'tcx>, - ) { - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; - for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) { - vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false); - } - } - - fn check_arg_for_power_alignment<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - ) -> bool { - // 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 - // - the first field of the struct is an aggregate whose - // recursively first field is a floating-point type greater than - // 4 bytes. - if cx.tcx.sess.target.os != "aix" { - return false; - } - if ty.is_floating_point() && ty.primitive_size(cx.tcx).bytes() > 4 { - return true; - } else if let Adt(adt_def, _) = ty.kind() - && adt_def.is_struct() - { - let struct_variant = adt_def.variant(VariantIdx::ZERO); - // Within a nested struct, all fields are examined to correctly - // report if any fields after the nested struct within the - // original struct are misaligned. - for struct_field in &struct_variant.fields { - let field_ty = cx.tcx.type_of(struct_field.did).instantiate_identity(); - if self.check_arg_for_power_alignment(cx, field_ty) { - return true; - } - } - } - return false; - } - - fn check_struct_for_power_alignment<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - item: &'tcx hir::Item<'tcx>, - ) { - let adt_def = cx.tcx.adt_def(item.owner_id.to_def_id()); - if adt_def.repr().c() - && !adt_def.repr().packed() - && cx.tcx.sess.target.os == "aix" - && !adt_def.all_fields().next().is_none() - { - let struct_variant_data = item.expect_struct().0; - for (index, ..) in struct_variant_data.fields().iter().enumerate() { - // 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. - if index != 0 { - let first_field_def = struct_variant_data.fields()[index]; - let def_id = first_field_def.def_id; - let ty = cx.tcx.type_of(def_id).instantiate_identity(); - if self.check_arg_for_power_alignment(cx, ty) { - cx.emit_span_lint( - USES_POWER_ALIGNMENT, - first_field_def.span, - UsesPowerAlignment, - ); - } - } - } - } - } -} - /// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in /// `extern "C" { }` blocks): /// @@ -1059,8 +1256,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { hir::ItemKind::Static(ty, ..) | hir::ItemKind::Const(ty, ..) | hir::ItemKind::TyAlias(ty, ..) => { - self.check_ty_maybe_containing_foreign_fnptr( - cx, + ImproperCTypesLint{cx}.check_type_for_external_abi_fnptr( + CItemKind::Definition, ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), ); @@ -1070,7 +1267,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { // Structs are checked based on if they follow the power alignment // rule (under repr(C)). hir::ItemKind::Struct(..) => { - self.check_struct_for_power_alignment(cx, item); + ImproperCTypesLint{cx}.check_struct_for_power_alignment(item); } // See `check_field_def`.. hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} @@ -1088,8 +1285,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { } fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { - self.check_ty_maybe_containing_foreign_fnptr( - cx, + ImproperCTypesLint{cx}.check_type_for_external_abi_fnptr( + CItemKind::Definition, field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), ); @@ -1112,11 +1309,11 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { _ => return, }; - let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; - if vis.is_internal_abi(abi) { - vis.check_fn(id, decl); + let lint = ImproperCTypesLint{cx}; + if fn_abi_is_internal(abi) { + lint.check_fn_for_external_abi_fnptr(CItemKind::Definition,id, decl); } else { - vis.check_foreign_fn(id, decl); + lint.check_foreign_fn(CItemKind::Definition, id, decl); } } } 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 9fda55c2a4807..dad453551c04e 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs @@ -39,6 +39,7 @@ pub fn params( } #[no_mangle] +#[allow(improper_ctypes_definitions)] pub fn returns( f1: extern "C-cmse-nonsecure-call" fn() -> u32, f2: extern "C-cmse-nonsecure-call" fn() -> u64, From 80c37b7273879eb631ca21184e956db44846d4ab Mon Sep 17 00:00:00 2001 From: niacdoial Date: Fri, 10 Jan 2025 00:37:54 +0100 Subject: [PATCH 7/9] lint ImproperCTypes: more major reworks - removed special-case logic for a few cases (including the unit type, which is now only checked for in one place) - moved a lot of type-checking code in their dedicated visit_* methods - reworked FfiResult type to handle multiple diagnostics per type (currently imperfect due to type caching) - reworked the messages around CStr and CString --- compiler/rustc_lint/messages.ftl | 14 +- .../rustc_lint/src/types/improper_ctypes.rs | 593 +++++++++++------- tests/ui/lint/lint-ctypes.rs | 11 + 3 files changed, 386 insertions(+), 232 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 8d327a3a4a9e7..ec839777e5937 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -369,8 +369,17 @@ 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, and use `CStr::as_ptr()` or `CString::as_ptr()` +lint_improper_ctypes_cstr_help_mut = + consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer +lint_improper_ctypes_cstr_help_owned = + consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) +lint_improper_ctypes_cstr_help_unknown = + consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use (depending on the use case) `CStr::as_ptr()`, `CString::into_raw()` then `CString::from_raw()`, or a dedicated buffer lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout lint_improper_ctypes_dyn = trait objects have no C equivalent @@ -398,6 +407,7 @@ lint_improper_ctypes_pat_reason = pattern types have no C equivalent lint_improper_ctypes_sized_ptr_to_unsafe_type = this reference (`{$ty}`) is ABI-compatible with a C pointer, but `{$inner_ty}` itself does not have a C layout +lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field 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 diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 3648a1a1ba4d2..878500770d55b 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -105,22 +105,129 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cache: FxHashSet>, } +#[derive(Clone,Debug)] +struct FfiUnsafeReason<'tcx> { + ty: Ty<'tcx>, + reason: DiagMessage, + help: Option, + inner: Option>>, +} + +#[derive(Clone,Debug)] enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), - FfiUnsafe { - ty: Ty<'tcx>, - reason: DiagMessage, - help: Option, - }, - FfiUnsafeWrapper { - ty: Ty<'tcx>, - reason: DiagMessage, - help: Option, - wrapped: Box>, - }, + 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![FfiUnsafeReason{ty,help, reason: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(|reason| FfiUnsafeReason{ + ty, + help: help.clone(), + reason:note.clone(), + inner:Some(Box::new(reason)) + }) + .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::FfiSafe|Self::FfiUnsafe(..) => self, + Self::FfiPhantom(ty) => { + Self::FfiUnsafe(vec![FfiUnsafeReason{ + ty, + reason: fluent::lint_improper_ctypes_only_phantomdata, + help:None, + inner:None, + }]) + }, + } + } } +impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { + fn add_assign(&mut self, mut other: Self) { + // this function is fluffin' awful but that's because matching mutable references consumes them (?!) + // the function itself imitates the following piece of non-compiling code: + + // match (self, other) { + // (Self::FfiUnsafe(_), _) => { + // // nothing to do + // }, + // (_, Self::FfiUnsafe(_)) => { + // *self = other; + // }, + // (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { + // println!("kick me, both FfiPhantom, self({:?}) other({:?})", ty1, ty2); + // }, + // (Self::FfiSafe,Self::FfiPhantom(_)) => { + // *self = other; + // }, + // (_, Self::FfiSafe) => { + // // nothing to do + // }, + // } + + let s_disc = std::mem::discriminant(self); + let o_disc = std::mem::discriminant(&other); + if s_disc == o_disc { + match (self, &mut other) { + (Self::FfiUnsafe(ref mut s_inner),Self::FfiUnsafe(ref mut o_inner)) => { + s_inner.append(o_inner); + }, + (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { + println!("kick me, both FfiPhantom, self({:?}) other({:?})", ty1, ty2); + }, + (Self::FfiSafe,Self::FfiSafe) => {}, + _ => unreachable!(), + } + } else { + if let Self::FfiUnsafe(_) = self { + return; + } + match other { + Self::FfiUnsafe(o_inner) => { + // self is Safe or Phantom: Unsafe wins + *self = Self::FfiUnsafe(o_inner); + }, + Self::FfiSafe => { + // self is always "wins" + return; + }, + Self::FfiPhantom(o_inner) => { + // self is Safe: Phantom wins + *self = Self::FfiPhantom(o_inner); + }, + } + } + } +} +impl<'tcx> std::ops::Add> for FfiResult<'tcx> { + type Output = FfiResult<'tcx>; + fn add(mut self, other: Self) -> Self::Output { + self += other; + self + } +} + + /// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it #[derive(Clone, Copy)] enum TypeSizedness { @@ -272,22 +379,13 @@ enum CTypesVisitorState{ // 0100: function return // 1000: used in declared function StaticTy = 0b0010, - StaticInner = 0b0011, ArgumentTyInDefinition = 0b1000, ReturnTyInDefinition = 0b1100, - ArgumentInnerInDefinition = 0b1001, - ReturnInnerInDefinition = 0b1101, ArgumentTyInDeclaration = 0b0000, ReturnTyInDeclaration = 0b0100, - ArgumentInnerInDeclaration = 0b0001, - ReturnInnerInDeclaration = 0b0101, } impl CTypesVisitorState{ - /// wether we are being directly used in a function or static - fn has_direct_use(self) -> bool { - ((self as u8) & 0b0001) == 0 - } /// wether the type is used (directly or not) in a static variable fn is_in_static(self) -> bool { ((self as u8) & 0b0010) != 0 @@ -312,21 +410,6 @@ impl CTypesVisitorState{ } ret } - /// get a new CTypesVisitorState from the current one, to visit the current type's inner types - fn to_inner_ty(self) -> Self{ - //(self as u8 | 0b0001).try_into().unwrap() - match self{ - Self::StaticTy|Self::StaticInner => Self::StaticInner, - Self::ArgumentTyInDefinition|Self::ArgumentInnerInDefinition - => Self::ArgumentInnerInDefinition, - Self::ReturnTyInDefinition|Self::ReturnInnerInDefinition - => Self::ReturnInnerInDefinition, - Self::ArgumentTyInDeclaration|Self::ArgumentInnerInDeclaration - => Self::ArgumentInnerInDeclaration, - Self::ReturnTyInDeclaration|Self::ReturnInnerInDeclaration - => Self::ReturnInnerInDeclaration, - } - } } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { @@ -346,18 +429,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { CItemKind::Declaration => CTypesVisitorState::ArgumentTyInDeclaration, CItemKind::Definition => CTypesVisitorState::ArgumentTyInDefinition, }; + + let mut all_ffires = FfiSafe; + for arg in sig.inputs() { - match self.visit_type(state, *arg) { - FfiSafe => {} - r => { - return FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }; - } - } + let ffi_res = self.visit_type(state, None, *arg); + all_ffires += ffi_res.forbid_phantom().wrap_all( + ty, + fluent::lint_improper_ctypes_fnptr_indirect_reason, + None, + ); } let ret_ty = sig.output(); @@ -366,17 +447,76 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { CItemKind::Definition => CTypesVisitorState::ReturnTyInDefinition, }; - match self.visit_type(state, ret_ty) { - r @ (FfiSafe | FfiPhantom(_)) => r, - r @ _ => FfiUnsafeWrapper { - ty: ty.clone(), - reason: fluent::lint_improper_ctypes_fnptr_indirect_reason, - help: None, - wrapped: Box::new(r), - }, + let ffi_res = self.visit_type(state, None, ret_ty); + all_ffires += ffi_res.forbid_phantom().wrap_all( + ty, + fluent::lint_improper_ctypes_fnptr_indirect_reason, + None, + ); + + all_ffires + } + + /// Checks if a simple numeric (int, float) type has an actual portable definition + /// for the compile target + fn visit_numeric( + &mut self, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { + // FIXME: for now, this is very incomplete, and seems to assume a x86_64 target + match ty.kind() { + ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_128bit, None), + ty::Int(..) | ty::Uint(..) | ty::Float(..) => + FfiResult::FfiSafe, + _ => bug!("visit_numeric is to be called with numeric (int, float) types"), } } + /// Return the right help for Cstring and Cstr-linked unsafety + fn visit_cstr( + &mut self, + outer_ty: Option>, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!( + matches!(ty.kind(), ty::Adt(def, _) + if matches!( + // TODO also use that trick to separate closures from dyn, if possible + self.cx.tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + ) + ); + + // TODO look into many cases: own/CString, ref/CString, own/struct/CString, ref/struct/CString, own/struct/ref/CString, etc... + // maybe mutable: own/CString, own/struct/CString + // known mutable: ref/CString, ref/CStr, ref/struct/CString, own/struct/ref/CString own/struct/ref/CStr + // should be impossible: anything/own/CStr + let help = if let Some(outer_ty) = outer_ty { + match outer_ty.kind() { + ty::Ref(..)|ty::RawPtr(..) => { + if outer_ty.is_mutable_ptr() { + fluent::lint_improper_ctypes_cstr_help_mut + } else { + fluent::lint_improper_ctypes_cstr_help_const + } + }, + ty::Adt(..) if outer_ty.boxed_ty().is_some() => + fluent::lint_improper_ctypes_cstr_help_owned, + _ => fluent::lint_improper_ctypes_cstr_help_unknown, + } + } else { + fluent::lint_improper_ctypes_cstr_help_owned + }; + + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_cstr_reason, + Some(help), + ) + } + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" fn visit_indirection( &mut self, @@ -385,8 +525,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { inner_ty: Ty<'tcx>, indirection_type: IndirectionType, ) -> FfiResult<'tcx> { - use FfiResult::*; let tcx = self.cx.tcx; + + if let ty::Adt(def, _) = inner_ty.kind() { + if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) = tcx.get_diagnostic_name(def.did()) { + // we have better error messages when checking for C-strings directly + let mut cstr_res = self.visit_cstr(Some(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().ty = ty; + }, + _ => unreachable!(), + } + let note = match indirection_type { + IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, + }; + return cstr_res.wrap_all(ty, note, None); + } else { + return cstr_res; + } + } + } + match get_type_sizedness(self.cx, inner_ty) { TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { // there's a nuance on what this lint should do for @@ -409,18 +576,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, // and having the full type information is necessary to compile the function. if state.is_in_defined_function() { - return FfiSafe; + return FfiResult::FfiSafe; } else { - let inner_res = self.visit_type(state.to_inner_ty(), inner_ty); - return match inner_res { - FfiSafe => inner_res, - _ => FfiUnsafeWrapper { - ty, - reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, - wrapped: Box::new(inner_res), - help: None, - }, - }; + return self.visit_type(state, Some(ty), inner_ty) + .forbid_phantom() + .wrap_all(ty, fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, None); } } TypeSizedness::NotYetKnown => { @@ -441,24 +601,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // wether 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. - return self.visit_type(state.to_inner_ty(), inner_ty); + return self.visit_type(state, Some(ty), inner_ty); } TypeSizedness::UnsizedWithMetadata => { let help = match inner_ty.kind() { ty::Str => Some(fluent::lint_improper_ctypes_str_help), ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), - ty::Adt(def, _) - if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union) - && matches!( - // TODO also use that trick to separate closures from dyn, if possible - tcx.get_diagnostic_name(def.did()), - Some(sym::cstring_type | sym::cstr_type) - ) - // TODO vv here vv : originally used acc.base_ty, needs more generic - && !ty.is_mutable_ptr() => - { - Some(fluent::lint_improper_ctypes_cstr_help) - } _ => None, }; let reason = match indirection_type { @@ -466,7 +614,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, }; - FfiUnsafe { ty, reason, help } + FfiResult::new_with_reason(ty, reason, help) } } } @@ -485,12 +633,18 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. let field_ty = get_type_from_field(self.cx, field, args); - match self.visit_type(state.to_inner_ty(), field_ty) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - - false + let ffi_res = self.visit_type(state, Some(ty), field_ty); + debug_assert!(!matches!( + // 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 here + ffi_res, + FfiUnsafe(ref reasons) + if matches!( + (reasons.len(),reasons.first()), + (1,Some(FfiUnsafeReason{ty,..})) if ty.is_unit() + ) + )); + return ffi_res; } else { // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all // `PhantomData`). @@ -500,23 +654,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { false }; + let mut all_ffires = FfiSafe; // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { let field_ty = get_type_from_field(self.cx, field, args); - all_phantom &= match self.visit_type(state.to_inner_ty(), field_ty) { - FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } | FfiUnsafeWrapper { ty, .. } if ty.is_unit() => false, + all_phantom &= match self.visit_type(state, Some(ty), field_ty) { FfiPhantom(..) => true, - r @ (FfiUnsafe { .. } | FfiUnsafeWrapper { .. }) => return r, + r @ (FfiUnsafe { .. } | FfiSafe) => {all_ffires += r; false}, } } - if all_phantom { + if matches!(all_ffires, FfiUnsafe(..)) { + all_ffires.wrap_all(ty, fluent::lint_improper_ctypes_struct_dueto, None) + } 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 } @@ -530,52 +684,51 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { debug_assert!(matches!(def.adt_kind(), AdtKind::Struct|AdtKind::Union)); - use FfiResult::*; if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_layout_reason } else { fluent::lint_improper_ctypes_union_layout_reason }, - help: if def.is_struct() { + if def.is_struct() { Some(fluent::lint_improper_ctypes_struct_layout_help) } else { Some(fluent::lint_improper_ctypes_union_layout_help) }, - }; + ); } let is_non_exhaustive = def.non_enum_variant().is_field_list_non_exhaustive(); if is_non_exhaustive && !def.did().is_local() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_non_exhaustive } else { fluent::lint_improper_ctypes_union_non_exhaustive }, - help: None, - }; + None, + ); } if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_fieldless_reason } else { fluent::lint_improper_ctypes_union_fieldless_reason }, - help: if def.is_struct() { + if def.is_struct() { Some(fluent::lint_improper_ctypes_struct_fieldless_help) } else { Some(fluent::lint_improper_ctypes_union_fieldless_help) }, - }; + ); } self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) @@ -592,7 +745,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { use FfiResult::*; if def.variants().is_empty() { - // Empty enums are implicitely handled as the empty type: + // Empty enums are implicitely handled as the never type: // values for them must never be constructed, // functions using them as argument or return must... err. // TODO @@ -603,7 +756,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option` and `Result` - if let Some(ty) = + if let Some(inner_ty) = repr_nullable_ptr( self.cx.tcx, self.cx.typing_env(), @@ -611,21 +764,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if state.is_in_defined_function(){CItemKind::Definition}else{CItemKind::Declaration}, ) { - return self.visit_type(state.to_inner_ty(), ty); + return self.visit_type(state, Some(ty), inner_ty); } - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; + fluent::lint_improper_ctypes_enum_repr_reason, + Some(fluent::lint_improper_ctypes_enum_repr_help), + ); } let non_local_def = non_local_and_non_exhaustive(def); // Check the contained variants. let ret = def.variants().iter().try_for_each(|variant| { check_non_exhaustive_variant(non_local_def, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + .map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?; match self.visit_variant_fields(state, ty, def, variant, args) { // TODO no need to pick only one @@ -645,6 +798,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { fn visit_type( &mut self, state: CTypesVisitorState, + outer_ty: Option>, ty: Ty<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; @@ -669,21 +823,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match def.adt_kind() { AdtKind::Struct | AdtKind::Union => { - // I thought CStr could not be reached here: + // I thought CStr (not CString) could not be reached here: // - not using an indirection would cause a compile error prior to this lint // - and using one would cause the lint to catch on the indirection before reaching its pointee // but for some reason one can just go and write function *pointers* like that: // `type Foo = extern "C" fn(::std::ffi::CStr);` if let Some(sym::cstring_type|sym::cstr_type) = - tcx.get_diagnostic_name(def.did()) - { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_cstr_reason, - help: Some(fluent::lint_improper_ctypes_cstr_help), // TODO look into many cases: own/CString, ref/CString, own/struct/CString, ref/struct/CString, own/struct/ref/CString, etc... - }; + // TODO also use that trick to separate closures from dyn, if possible + tcx.get_diagnostic_name(def.did()) { + return self.visit_cstr(outer_ty, ty); } - self.visit_struct_union(state, ty, def, args) } AdtKind::Enum => { @@ -692,56 +841,71 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - ty::Char => FfiUnsafe { + ty::Char => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_char_reason, - help: Some(fluent::lint_improper_ctypes_char_help), - }, + fluent::lint_improper_ctypes_char_reason, + Some(fluent::lint_improper_ctypes_char_help), + ), - ty::Pat(..) => FfiUnsafe { + ty::Pat(..) => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_pat_reason, - help: Some(fluent::lint_improper_ctypes_pat_help), - }, + fluent::lint_improper_ctypes_pat_reason, + Some(fluent::lint_improper_ctypes_pat_help), + ), - // FIXME: this should probably be architecture-dependant - // same with some ty::Float variants. - ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None } - } + // types which likely have a stable representation, depending on the target architecture + ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty), // Primitive types with a stable representation. - ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, + ty::Bool | ty::Never => FfiSafe, - ty::Slice(_) => FfiUnsafe { + ty::Slice(_) => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_slice_reason, - help: Some(fluent::lint_improper_ctypes_slice_help), - }, + fluent::lint_improper_ctypes_slice_reason, + Some(fluent::lint_improper_ctypes_slice_help), + ), - ty::Dynamic(..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None } - } + ty::Dynamic(..) => FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_dyn, + None, + ), - ty::Str => FfiUnsafe { + ty::Str => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_str_reason, - help: Some(fluent::lint_improper_ctypes_str_help), - }, + fluent::lint_improper_ctypes_str_reason, + Some(fluent::lint_improper_ctypes_str_help), + ), - ty::Tuple(tuple) => - // TODO do we move the "unit types allowed as fields" here? assuming they are also allowed behind indirections - // ...doesn't seem so. Existing logic doesn't like Boxes and refs to those. - if tuple.is_empty() && state.is_in_function_return() && state.has_direct_use() { - // C functions can return void + ty::Tuple(tuple) => { + let empty_and_safe = if tuple.is_empty() { + if let Some(outer_ty) = outer_ty { + match outer_ty.kind() { + // `()` fields are FFI-safe! + ty::Adt(..) => true, + ty::RawPtr(..) => true, + // most of those are not even reachable, + // but let's not worry about checking that here + _ => false, + } + } else { + // C functions can return void + state.is_in_function_return() + } + } else { + false + }; + + if empty_and_safe{ FfiSafe } else { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - } - }, + fluent::lint_improper_ctypes_tuple_reason, + Some(fluent::lint_improper_ctypes_tuple_help), + ) + } + }, ty::RawPtr(ty, _) if match ty.kind() { @@ -760,27 +924,27 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Array(inner_ty, _) => { - if state.has_direct_use() && !state.is_in_static() { + if outer_ty.is_none() && !state.is_in_static() { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. - FfiUnsafe{ + FfiResult::new_with_reason( ty, - reason:fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - } + fluent::lint_improper_ctypes_array_reason, + Some(fluent::lint_improper_ctypes_array_help), + ) } else { - self.visit_type(state.to_inner_ty(), inner_ty) + self.visit_type(state, Some(ty), inner_ty) } } ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); if fn_abi_is_internal(sig.abi()) { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_fnptr_reason, - help: Some(fluent::lint_improper_ctypes_fnptr_help), - } + fluent::lint_improper_ctypes_fnptr_reason, + Some(fluent::lint_improper_ctypes_fnptr_help), + ) } else { let mode = if state.is_in_defined_function() { CItemKind::Definition @@ -795,9 +959,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. - ty::Alias(ty::Opaque, ..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None } - } + ty::Alias(ty::Opaque, ..) => FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_opaque, + None, + ), // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, // so they are currently ignored for the purposes of this lint. @@ -845,11 +1011,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .visit_with(&mut ProhibitOpaqueTypes) .break_value() { - FfiResult::FfiUnsafe{ + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_opaque, - help: None, - } + fluent::lint_improper_ctypes_opaque, + None, + ) } else { FfiResult::FfiSafe } @@ -866,7 +1032,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiResult::FfiSafe => (), ffi_res @ _ => return ffi_res, } - self.visit_type(state, ty) + self.visit_type(state, None, ty) } fn check_for_fnptr( @@ -1121,64 +1287,31 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ fn_mode, ); } - FfiResult::FfiUnsafe { ty, reason, help } => { - self.emit_ffi_unsafe_type_lint( - ty.clone(), - sp, - vec![ImproperCTypesLayer { - ty, - help, - note: reason, - span_note: None, // filled later - inner_ty: None, - }], - fn_mode, - ); - } - ffir @ FfiResult::FfiUnsafeWrapper { .. } => { - let mut ffiresult_recursor = ControlFlow::Continue(&ffir); + FfiResult::FfiUnsafe(reasons) => for reason in reasons { + let mut ffiresult_recursor = ControlFlow::Continue(&reason); let mut cimproper_layers: Vec> = vec![]; // this whole while block converts the arbitrarily-deep // FfiResult stack to an ImproperCTypesLayer Vec - while let ControlFlow::Continue(ref ffir_rec) = ffiresult_recursor { - match ffir_rec { - FfiResult::FfiPhantom(ty) => { - 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: None, - note: fluent::lint_improper_ctypes_only_phantomdata, - span_note: None, // filled later - }); - ffiresult_recursor = ControlFlow::Break(()); - } - FfiResult::FfiUnsafe { ty, reason, help } - | FfiResult::FfiUnsafeWrapper { ty, reason, help, .. } => { - 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: reason.clone(), - span_note: None, // filled later - }); - - if let FfiResult::FfiUnsafeWrapper { wrapped, .. } = ffir_rec { - ffiresult_recursor = ControlFlow::Continue(wrapped.as_ref()); - } else { - ffiresult_recursor = ControlFlow::Break(()); - } - } - FfiResult::FfiSafe => { - bug!("malformed FfiResult stack: it should be unsafe all the way down") - } - }; + while let ControlFlow::Continue(FfiUnsafeReason{ + ref ty, ref reason, ref help, ref 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: reason.clone(), + span_note: None, // filled later + }); + + if let Some(inner) = inner { + ffiresult_recursor = ControlFlow::Continue(inner.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } } // should always have at least one type let last_ty = cimproper_layers.last().unwrap().ty.clone(); diff --git a/tests/ui/lint/lint-ctypes.rs b/tests/ui/lint/lint-ctypes.rs index 22617e0ebb99b..b9b401e4e4cde 100644 --- a/tests/ui/lint/lint-ctypes.rs +++ b/tests/ui/lint/lint-ctypes.rs @@ -53,6 +53,12 @@ pub struct UnsizedStructBecauseDyn { 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); @@ -83,6 +89,11 @@ extern "C" { pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` + pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); + //~^ ERROR: uses type `char` + //~^^ ERROR: uses type `&dyn Debug` + // (possible FIXME: the in-struct `char` field doesn't get a warning due ^^) + //~^^^^ ERROR: uses type `&[u8]` pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` From 853d70d0699a93aba51f2096b22328ef482977a2 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 18 Jan 2025 18:30:21 +0100 Subject: [PATCH 8/9] lint ImproperCTypes: add considerations for which values can be sourced from non-rust code --- compiler/rustc_lint/messages.ftl | 8 +- compiler/rustc_lint/src/types.rs | 34 +- .../rustc_lint/src/types/improper_ctypes.rs | 557 ++++++++++-------- tests/ui/abi/nullable-pointer-ffi-compat.rs | 1 + tests/ui/asm/issue-97490.rs | 2 + ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 8 +- .../repr-rust-is-undefined.stderr | 18 + tests/ui/lint/lint-ctypes-113436-1.stderr | 12 + tests/ui/lint/lint-ctypes-73249-3.stderr | 6 + tests/ui/lint/lint-ctypes-cstr.rs | 22 +- tests/ui/lint/lint-ctypes-cstr.stderr | 63 +- tests/ui/lint/lint-ctypes-fn.rs | 13 +- tests/ui/lint/lint-ctypes-fn.stderr | 84 ++- tests/ui/lint/lint-ctypes-tykind-fuzz.rs | 119 ++-- tests/ui/lint/lint-ctypes-tykind-fuzz.stderr | 202 ++++--- tests/ui/lint/lint-ctypes.rs | 5 + tests/ui/lint/lint-ctypes.stderr | 105 +++- 17 files changed, 771 insertions(+), 488 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index ec839777e5937..2af981d5ee214 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -400,9 +400,13 @@ lint_improper_ctypes_only_phantomdata = composed only of `PhantomData` lint_improper_ctypes_opaque = opaque types have no C equivalent -lint_improper_ctypes_pat_help = consider using the base type instead +lint_improper_ctypes_pat_intrange_help = consider using the base type instead +lint_improper_ctypes_pat_intrange_reason = integers constrained to a given range cannot have their value be provided by non-rust code -lint_improper_ctypes_pat_reason = pattern types have no C equivalent +lint_improper_ctypes_ptr_validity_help = consider using a raw pointer, or wrapping `{$ty}` in an `Option<_>` +lint_improper_ctypes_ptr_validity_reason = + boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code lint_improper_ctypes_sized_ptr_to_unsafe_type = this reference (`{$ty}`) is ABI-compatible with a C pointer, but `{$inner_ty}` itself does not have a C layout diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 675e704a36179..0db60d2f103fb 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -4,7 +4,7 @@ use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; use rustc_hir::{Expr, ExprKind, 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, AdtKind, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::{Span, Symbol, source_map, sym}; use tracing::debug; @@ -981,6 +981,38 @@ pub(crate) fn repr_nullable_ptr<'tcx>( None } +/// determines wether or not `outer_ty` is an option-like enum, with the same size as its contained type, `ty`. +/// this ASSUMES that `ty` is a type that is already 'inside' of `outer_ty`. +fn is_outer_optionlike_around_ty<'tcx>( + cx: &LateContext<'tcx>, + outer_ty: Ty<'tcx>, + ty: Ty<'tcx>, +) -> bool { + // three things to check to be sure outer_ty is option-like (since we know we reached the current ty from there) + // That outer_ty is an enum, that this enum doesn't have a defined discriminant representation, + // and the the outer_ty's size is that of ty. + if let ty::Adt(def, _) = outer_ty.kind() { + if !matches!(def.adt_kind(), AdtKind::Enum) + || def.repr().c() + || def.repr().transparent() + || def.repr().int.is_none() + { + false + } else { + let (tcx, typing_env) = (cx.tcx, cx.typing_env()); + + // see the insides of super::repr_nullable_ptr() + let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, typing_env).ok(); + match (compute_size_skeleton(ty), compute_size_skeleton(outer_ty)) { + (Some(sk1), Some(sk2)) => sk1.same_size(sk2), + _ => false, + } + } + } else { + false + } +} + declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 878500770d55b..f05906488d575 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::{ }; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; -use rustc_type_ir::{Binder,FnSig}; +use rustc_type_ir::{Binder, FnSig}; use tracing::debug; use super::{ @@ -35,13 +35,10 @@ fn fn_abi_is_internal(abi: ExternAbi) -> bool { ) } - - // a shorthand for an often used lifetime-region normalisation step #[inline] -fn normalize_if_possible<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx>{ - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) - .unwrap_or(ty) +fn normalize_if_possible<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty) } // getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple) @@ -55,8 +52,9 @@ fn get_type_from_field<'tcx>( normalize_if_possible(cx, field_ty) } - /// Check a variant of a non-exhaustive enum for improper ctypes +/// returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and +/// "we have FFI-unsafety due to a non-exhaustive enum variant" /// /// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". /// This includes linting, on a best-effort basis. There are valid additions that are unlikely. @@ -64,10 +62,10 @@ fn get_type_from_field<'tcx>( /// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", /// so we don't need the lint to account for it. /// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. -pub(crate) fn check_non_exhaustive_variant( +pub(crate) fn flag_non_exhaustive_variant( non_local_def: bool, variant: &ty::VariantDef, -) -> ControlFlow { +) -> (bool, bool) { // non_exhaustive suggests it is possible that someone might break ABI // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 // so warn on complex enums being used outside their crate @@ -76,16 +74,16 @@ pub(crate) fn check_non_exhaustive_variant( // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) if variant_has_complex_ctor(variant) { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive); + return (true, false); } } let non_exhaustive_variant_fields = variant.is_field_list_non_exhaustive(); if non_exhaustive_variant_fields && !variant.def_id.is_local() { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant); + return (false, true); } - ControlFlow::Continue(()) + (false, false) } fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { @@ -105,7 +103,7 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cache: FxHashSet>, } -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] struct FfiUnsafeReason<'tcx> { ty: Ty<'tcx>, reason: DiagMessage, @@ -113,7 +111,7 @@ struct FfiUnsafeReason<'tcx> { inner: Option>>, } -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), @@ -123,7 +121,7 @@ enum FfiResult<'tcx> { 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![FfiUnsafeReason{ty,help, reason:note, inner:None}]) + Self::FfiUnsafe(vec![FfiUnsafeReason { ty, help, reason: note, inner: None }]) } /// If the FfiUnsafe variant, 'wraps' all reasons, @@ -132,16 +130,17 @@ impl<'tcx> FfiResult<'tcx> { fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { match self { Self::FfiUnsafe(this) => { - let unsafeties = this.into_iter() - .map(|reason| FfiUnsafeReason{ + let unsafeties = this + .into_iter() + .map(|reason| FfiUnsafeReason { ty, help: help.clone(), - reason:note.clone(), - inner:Some(Box::new(reason)) + reason: note.clone(), + inner: Some(Box::new(reason)), }) .collect(); Self::FfiUnsafe(unsafeties) - }, + } r @ _ => r, } } @@ -149,22 +148,23 @@ impl<'tcx> FfiResult<'tcx> { /// Otherwise, keep unchanged. fn forbid_phantom(self) -> Self { match self { - Self::FfiSafe|Self::FfiUnsafe(..) => self, - Self::FfiPhantom(ty) => { - Self::FfiUnsafe(vec![FfiUnsafeReason{ - ty, - reason: fluent::lint_improper_ctypes_only_phantomdata, - help:None, - inner:None, - }]) - }, + Self::FfiSafe | Self::FfiUnsafe(..) => self, + Self::FfiPhantom(ty) => Self::FfiUnsafe(vec![FfiUnsafeReason { + ty, + reason: fluent::lint_improper_ctypes_only_phantomdata, + help: None, + inner: None, + }]), } } } impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { fn add_assign(&mut self, mut other: Self) { - // this function is fluffin' awful but that's because matching mutable references consumes them (?!) + // 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 + + // this function is awful to look but that's because matching mutable references consumes them (?!) // the function itself imitates the following piece of non-compiling code: // match (self, other) { @@ -175,7 +175,7 @@ impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { // *self = other; // }, // (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { - // println!("kick me, both FfiPhantom, self({:?}) other({:?})", ty1, ty2); + // println!("whoops, both FfiPhantom: self({:?}) += other({:?})", ty1, ty2); // }, // (Self::FfiSafe,Self::FfiPhantom(_)) => { // *self = other; @@ -189,13 +189,13 @@ impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { let o_disc = std::mem::discriminant(&other); if s_disc == o_disc { match (self, &mut other) { - (Self::FfiUnsafe(ref mut s_inner),Self::FfiUnsafe(ref mut o_inner)) => { + (Self::FfiUnsafe(ref mut s_inner), Self::FfiUnsafe(ref mut o_inner)) => { s_inner.append(o_inner); - }, - (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { - println!("kick me, both FfiPhantom, self({:?}) other({:?})", ty1, ty2); - }, - (Self::FfiSafe,Self::FfiSafe) => {}, + } + (Self::FfiPhantom(ref ty1), Self::FfiPhantom(ty2)) => { + debug!("whoops: both FfiPhantom, self({:?}) += other({:?})", ty1, ty2); + } + (Self::FfiSafe, Self::FfiSafe) => {} _ => unreachable!(), } } else { @@ -206,15 +206,15 @@ impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { Self::FfiUnsafe(o_inner) => { // self is Safe or Phantom: Unsafe wins *self = Self::FfiUnsafe(o_inner); - }, + } Self::FfiSafe => { // self is always "wins" return; - }, + } Self::FfiPhantom(o_inner) => { // self is Safe: Phantom wins *self = Self::FfiPhantom(o_inner); - }, + } } } } @@ -227,7 +227,6 @@ impl<'tcx> std::ops::Add> for FfiResult<'tcx> { } } - /// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it #[derive(Clone, Copy)] enum TypeSizedness { @@ -281,12 +280,8 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type return TypeSizedness::UnsizedWithMetadata; } - // FIXME: double-check: non-exhaustive structs from other crates are assumed to be ?Sized, right? - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); - if is_non_exhaustive && !def.did().is_local() { - return TypeSizedness::NotYetKnown; - } + // 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"); @@ -295,11 +290,12 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type let variant = def.non_enum_variant(); // only the last field may be !Sized (or ?Sized in the case of type params) - let last_field = &variant.fields[(&variant.fields.len()-1).into()]; - // let last_field = match &variant.fields.iter().last(){ // TODO performance - // Some(last_field) => last_field, - // None => bug!("Empty struct should be Sized, right?"), // TODO: nonexhaustive empty struct from another crate/module - // }; + let last_field = match (&variant.fields).iter().last(){ + Some(last_field) => last_field, + // even nonexhaustive-empty structs from another crate are considered Sized + // (eventhough one could add a !Sized field to them) + None => bug!("Empty struct should be Sized, right?"), // + }; let field_ty = get_type_from_field(cx, last_field, args); match get_type_sizedness(cx, field_ty) { s @ (TypeSizedness::UnsizedWithMetadata @@ -371,10 +367,9 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type } #[repr(u8)] -#[derive(Clone,Copy,Debug)] -enum CTypesVisitorState{ +#[derive(Clone, Copy, Debug)] +enum CTypesVisitorState { // bitflags: - // 0001: inner type // 0010: static // 0100: function return // 1000: used in declared function @@ -385,7 +380,7 @@ enum CTypesVisitorState{ ReturnTyInDeclaration = 0b0100, } -impl CTypesVisitorState{ +impl CTypesVisitorState { /// wether the type is used (directly or not) in a static variable fn is_in_static(self) -> bool { ((self as u8) & 0b0010) != 0 @@ -394,7 +389,7 @@ impl CTypesVisitorState{ fn is_in_function_return(self) -> bool { let ret = ((self as u8) & 0b0100) != 0; #[cfg(debug_assertions)] - if ret{ + if ret { assert!(!self.is_in_static()); } ret @@ -405,23 +400,31 @@ impl CTypesVisitorState{ fn is_in_defined_function(self) -> bool { let ret = ((self as u8) & 0b1000) != 0; #[cfg(debug_assertions)] - if ret{ + if ret { assert!(!self.is_in_static()); } ret } + + /// wether the value for that type might come from the non-rust side of a FFI boundary + fn value_may_be_unchecked(self) -> bool { + // function declarations are assumed to be rust-caller, non-rust-callee + // function definitions are assumed to be maybe-not-rust-caller, rust-callee + // FnPtrs are... well, nothing's certain about anything. (TODO need more flags in enum?) + // Same with statics. + if self.is_in_static() { + true + } else if self.is_in_defined_function() { + !self.is_in_function_return() + } else { + self.is_in_function_return() + } + } } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - /// Checks wether an `extern "ABI" fn` function pointer is indeed FFI-safe to call - fn visit_fnptr( - &mut self, - // TODO this feels wrong but rustc doesn't compile withoug that :') - mode: CItemKind, - ty: Ty<'tcx>, - sig: Sig<'tcx> - ) -> FfiResult<'tcx> { + fn visit_fnptr(&mut self, mode: CItemKind, ty: Ty<'tcx>, sig: Sig<'tcx>) -> FfiResult<'tcx> { use FfiResult::*; debug_assert!(!fn_abi_is_internal(sig.abi())); let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); @@ -459,68 +462,52 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if a simple numeric (int, float) type has an actual portable definition /// for the compile target - fn visit_numeric( - &mut self, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { + fn visit_numeric(&mut self, ty: Ty<'tcx>) -> FfiResult<'tcx> { // FIXME: for now, this is very incomplete, and seems to assume a x86_64 target match ty.kind() { - ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => - FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_128bit, None), - ty::Int(..) | ty::Uint(..) | ty::Float(..) => - FfiResult::FfiSafe, + ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_128bit, None) + } + ty::Int(..) | ty::Uint(..) | ty::Float(..) => FfiResult::FfiSafe, _ => bug!("visit_numeric is to be called with numeric (int, float) types"), } } /// Return the right help for Cstring and Cstr-linked unsafety - fn visit_cstr( - &mut self, - outer_ty: Option>, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { - debug_assert!( - matches!(ty.kind(), ty::Adt(def, _) - if matches!( - // TODO also use that trick to separate closures from dyn, if possible - self.cx.tcx.get_diagnostic_name(def.did()), - Some(sym::cstring_type | sym::cstr_type) - ) + fn visit_cstr(&mut self, outer_ty: Option>, 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) ) - ); + )); - // TODO look into many cases: own/CString, ref/CString, own/struct/CString, ref/struct/CString, own/struct/ref/CString, etc... - // maybe mutable: own/CString, own/struct/CString - // known mutable: ref/CString, ref/CStr, ref/struct/CString, own/struct/ref/CString own/struct/ref/CStr - // should be impossible: anything/own/CStr let help = if let Some(outer_ty) = outer_ty { match outer_ty.kind() { - ty::Ref(..)|ty::RawPtr(..) => { + ty::Ref(..) | ty::RawPtr(..) => { if outer_ty.is_mutable_ptr() { fluent::lint_improper_ctypes_cstr_help_mut } else { fluent::lint_improper_ctypes_cstr_help_const } - }, - ty::Adt(..) if outer_ty.boxed_ty().is_some() => - fluent::lint_improper_ctypes_cstr_help_owned, + } + ty::Adt(..) if outer_ty.boxed_ty().is_some() => { + fluent::lint_improper_ctypes_cstr_help_owned + } _ => fluent::lint_improper_ctypes_cstr_help_unknown, } } else { fluent::lint_improper_ctypes_cstr_help_owned }; - FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_cstr_reason, - Some(help), - ) + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help)) } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe" fn visit_indirection( &mut self, state: CTypesVisitorState, + outer_ty: Option>, ty: Ty<'tcx>, inner_ty: Ty<'tcx>, indirection_type: IndirectionType, @@ -528,9 +515,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { 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()) { + 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(Some(ty), inner_ty); // always unsafe with one depth-one reason. + let mut cstr_res = self.visit_cstr(Some(ty), inner_ty); // always unsafe with one depth-one reason. // Cstr pointer have metadata, CString is Sized if diag_name == sym::cstr_type { @@ -539,7 +528,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match cstr_res { FfiResult::FfiUnsafe(ref mut reasons) => { reasons.first_mut().unwrap().ty = ty; - }, + } _ => unreachable!(), } let note = match indirection_type { @@ -554,7 +543,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - match get_type_sizedness(self.cx, inner_ty) { + // 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 a value given by non-rust code? + // this block deals with the first two. + let mut ffi_res = match get_type_sizedness(self.cx, inner_ty) { TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { // there's a nuance on what this lint should do for // function definitions (`extern "C" fn fn_name(...) {...}`) @@ -576,11 +570,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // For extern function definitions, however, in the case where the type is opaque caller-side, it is not opaque callee-side, // and having the full type information is necessary to compile the function. if state.is_in_defined_function() { - return FfiResult::FfiSafe; + FfiResult::FfiSafe } else { - return self.visit_type(state, Some(ty), inner_ty) - .forbid_phantom() - .wrap_all(ty, fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, None); + return self.visit_type(state, Some(ty), inner_ty).forbid_phantom().wrap_all( + ty, + fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type, + None, + ); } } TypeSizedness::NotYetKnown => { @@ -601,7 +597,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // wether 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. - return self.visit_type(state, Some(ty), inner_ty); + self.visit_type(state, Some(ty), inner_ty) } TypeSizedness::UnsizedWithMetadata => { let help = match inner_ty.kind() { @@ -614,9 +610,36 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref, IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box, }; - FfiResult::new_with_reason(ty, reason, help) + return FfiResult::new_with_reason(ty, reason, help); } - } + }; + + // and now the third concern (does the pointer type contain a non-zero assumption, and is the value given by non-rust code?) + ffi_res += if state.value_may_be_unchecked() { + let has_nonnull_assumption = match indirection_type { + IndirectionType::RawPtr => false, + IndirectionType::Ref | IndirectionType::Box => true, + }; + let has_optionlike_wrapper = if let Some(outer_ty) = outer_ty { + super::is_outer_optionlike_around_ty(self.cx, outer_ty, ty) + } else { + false + }; + + if has_nonnull_assumption && !has_optionlike_wrapper { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_ptr_validity_reason, + Some(fluent::lint_improper_ctypes_ptr_validity_help), + ) + } else { + FfiResult::FfiSafe + } + } else { + FfiResult::FfiSafe + }; + + ffi_res } /// Checks if the given `VariantDef`'s field types are "ffi-safe". @@ -661,7 +684,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let field_ty = get_type_from_field(self.cx, field, args); all_phantom &= match self.visit_type(state, Some(ty), field_ty) { FfiPhantom(..) => true, - r @ (FfiUnsafe { .. } | FfiSafe) => {all_ffires += r; false}, + r @ (FfiUnsafe { .. } | FfiSafe) => { + all_ffires += r; + false + } } } @@ -683,7 +709,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { def: ty::AdtDef<'tcx>, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { - debug_assert!(matches!(def.adt_kind(), AdtKind::Struct|AdtKind::Union)); + debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); if !def.repr().c() && !def.repr().transparent() { return FfiResult::new_with_reason( @@ -701,8 +727,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ); } - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); + let is_non_exhaustive = def.non_enum_variant().is_field_list_non_exhaustive(); if is_non_exhaustive && !def.did().is_local() { return FfiResult::new_with_reason( ty, @@ -746,24 +771,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if def.variants().is_empty() { // Empty enums are implicitely handled as the never type: - // values for them must never be constructed, - // functions using them as argument or return must... err. - // TODO + // TODO think about the FFI-safety of functions that use that 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() - { + if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option` and `Result` - if let Some(inner_ty) = - repr_nullable_ptr( - self.cx.tcx, - self.cx.typing_env(), - ty, - if state.is_in_defined_function(){CItemKind::Definition}else{CItemKind::Declaration}, - ) - { + if let Some(inner_ty) = repr_nullable_ptr( + self.cx.tcx, + self.cx.typing_env(), + ty, + if state.is_in_defined_function() { + CItemKind::Definition + } else { + CItemKind::Declaration + }, + ) { return self.visit_type(state, Some(ty), inner_ty); } @@ -776,21 +800,34 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let non_local_def = non_local_and_non_exhaustive(def); // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_local_def, variant) - .map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?; - - match self.visit_variant_fields(state, ty, def, variant, args) { - // TODO no need to pick only one - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } + + let (mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false); + def.variants().iter().for_each(|variant| { + let (nonex_enum, nonex_var) = flag_non_exhaustive_variant(non_local_def, variant); + nonexhaustive_flag |= nonex_enum; + nonexhaustive_variant_flag |= nonex_var; }); - if let ControlFlow::Break(result) = ret { - return result; - } - FfiSafe + if nonexhaustive_flag { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_non_exhaustive, None) + } else if nonexhaustive_variant_flag { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_non_exhaustive_variant, + None, + ) + } else { + def.variants() + .iter() + .map(|variant| { + self.visit_variant_fields(state, ty, def, variant, args) + // TODO: 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?) + .forbid_phantom() + }) + .reduce(|r1, r2| r1 + r2) + .unwrap() // always at least one variant if we hit this branch + } } /// Checks if the given type is "ffi-safe" (has a stable, well-defined @@ -816,7 +853,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match *ty.kind() { ty::Adt(def, args) => { if let Some(inner_ty) = ty.boxed_ty() { - return self.visit_indirection(state, ty, inner_ty, IndirectionType::Box); + return self.visit_indirection( + state, + outer_ty, + ty, + inner_ty, + IndirectionType::Box, + ); } if def.is_phantom_data() { return FfiPhantom(ty); @@ -828,16 +871,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // - and using one would cause the lint to catch on the indirection before reaching its pointee // but for some reason one can just go and write function *pointers* like that: // `type Foo = extern "C" fn(::std::ffi::CStr);` - if let Some(sym::cstring_type|sym::cstr_type) = - // TODO also use that trick to separate closures from dyn, if possible - tcx.get_diagnostic_name(def.did()) { + if let Some(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + { return self.visit_cstr(outer_ty, ty); } self.visit_struct_union(state, ty, def, args) } - AdtKind::Enum => { - self.visit_enum(state, ty, def, args) - } + AdtKind::Enum => self.visit_enum(state, ty, def, args), } } @@ -847,11 +888,28 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Some(fluent::lint_improper_ctypes_char_help), ), - ty::Pat(..) => FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_pat_reason, - Some(fluent::lint_improper_ctypes_pat_help), - ), + ty::Pat(pat_ty, _) => { + if state.value_may_be_unchecked() { + // you would think that int-range pattern types that exclude 0 would have Option layout optimisation + // they don't (see tests/ui/type/pattern_types/range_patterns.stderr) + // so there's no need to allow Option. + debug_assert!(matches!( + pat_ty.kind(), + ty::Int(..) | ty::Uint(..) | ty::Float(..) + )); + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_pat_intrange_reason, + Some(fluent::lint_improper_ctypes_pat_intrange_help), + ) + } else if let ty::Int(_) | ty::Uint(_) = pat_ty.kind() { + self.visit_numeric(pat_ty) + } else { + bug!( + "this lint was written when pattern types could only be integers constrained to ranges" + ) + } + } // types which likely have a stable representation, depending on the target architecture ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty), @@ -865,11 +923,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Some(fluent::lint_improper_ctypes_slice_help), ), - ty::Dynamic(..) => FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_dyn, - None, - ), + ty::Dynamic(..) => { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_dyn, None) + } ty::Str => FfiResult::new_with_reason( ty, @@ -896,7 +952,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { false }; - if empty_and_safe{ + if empty_and_safe { FfiSafe } else { FfiResult::new_with_reason( @@ -905,7 +961,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Some(fluent::lint_improper_ctypes_tuple_help), ) } - }, + } ty::RawPtr(ty, _) if match ty.kind() { @@ -917,10 +973,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::RawPtr(inner_ty, _) => { - return self.visit_indirection(state, ty, inner_ty, IndirectionType::RawPtr); + return self.visit_indirection( + state, + outer_ty, + ty, + inner_ty, + IndirectionType::RawPtr, + ); } ty::Ref(_, inner_ty, _) => { - return self.visit_indirection(state, ty, inner_ty, IndirectionType::Ref); + return self.visit_indirection(state, outer_ty, ty, inner_ty, IndirectionType::Ref); } ty::Array(inner_ty, _) => { @@ -959,11 +1021,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. - ty::Alias(ty::Opaque, ..) => FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_opaque, - None, - ), + ty::Alias(ty::Opaque, ..) => { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None) + } // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, // so they are currently ignored for the purposes of this lint. @@ -1007,25 +1067,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - if let Some(ty) = ty - .visit_with(&mut ProhibitOpaqueTypes) - .break_value() - { - FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_opaque, - None, - ) + if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes).break_value() { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None) } else { FfiResult::FfiSafe } } - fn check_for_type( - &mut self, - state: CTypesVisitorState, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { + fn check_for_type(&mut self, state: CTypesVisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { let ty = normalize_if_possible(self.cx, ty); match self.check_for_opaque_ty(ty) { @@ -1035,11 +1084,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { self.visit_type(state, None, ty) } - fn check_for_fnptr( - &mut self, - mode: CItemKind, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { + fn check_for_fnptr(&mut self, mode: CItemKind, ty: Ty<'tcx>) -> FfiResult<'tcx> { let ty = normalize_if_possible(self.cx, ty); match self.check_for_opaque_ty(ty) { @@ -1051,23 +1096,27 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); if fn_abi_is_internal(sig.abi()) { - bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one") + bug!( + "expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one" + ) } else { self.visit_fnptr(mode, ty, sig) } - }, - _ => bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not whtaever this is"), + } + _ => bug!( + "expected to inspect the type of an `extern \"ABI\"` FnPtr, not whtaever this is" + ), } } } /// common structure for functionality that is shared /// between ImproperCTypesDeclarations and ImproperCTypesDefinitions -struct ImproperCTypesLint<'c, 'tcx>{ - cx: &'c LateContext<'tcx> +struct ImproperCTypesLint<'c, 'tcx> { + cx: &'c LateContext<'tcx>, } -impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ +impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx> { fn check_arg_for_power_alignment( &mut self, ty: Ty<'tcx>, @@ -1174,22 +1223,22 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ } } - let mut visitor = FnPtrFinder {spans: Vec::new(), tys: Vec::new() }; + let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() }; ty.visit_with(&mut visitor); visitor.visit_ty_unambig(hir_ty); let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)); all_types - .map(|(fn_ptr_ty, span)|{ - // TODO this will probably lead to error deduplication: fix this - let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + .map(|(fn_ptr_ty, span)| { + // FIXME this will probably lead to error deduplication: fix this + let mut visitor = + ImproperCTypesVisitor { cx: self.cx, cache: FxHashSet::default() }; let ffi_res = visitor.check_for_fnptr(fn_mode, fn_ptr_ty); (span, ffi_res) }) - //.flatten() // TODO already planning for more - // even in function *definitions*, FnPtr:s are always function declarations. so it makes sense ...right? - .map(|(span, ffi_res)|self.process_ffi_result(span, ffi_res, fn_mode)) - .reduce(|_a:(),_b:()|()); + // even in function *definitions*, `FnPtr`s are always function declarations ...right? + // (TODO: we can't do that yet because one of rustc's crates can't compile if we do) + .for_each(|(span, ffi_res)| self.process_ffi_result(span, ffi_res, fn_mode)); //.drain(); } @@ -1199,19 +1248,13 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ &self, fn_mode: CItemKind, def_id: LocalDefId, - decl: &'tcx hir::FnDecl<'_> + decl: &'tcx hir::FnDecl<'_>, ) { let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { self.check_type_for_external_abi_fnptr(fn_mode, input_hir, *input_ty); - // for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { - // // no CTypesVisitorState needed, it's overwritten as soon as the FnPtr is entered - // // can default to ArgumentTyInDeclaration if needed - // let res = todo!(fn_ptr_ty); - // res.iter().map(|res|self.process_ffi_result(span, res)).drain(); - // } } if let hir::FnRetTy::Return(ret_hir) = decl.output { @@ -1219,15 +1262,10 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ } } - /// Check that an extern "ABI" static variable is of a ffi-safe type - fn check_foreign_static( - &self, - id: hir::OwnerId, - span: Span - ) { + fn check_foreign_static(&self, id: hir::OwnerId, span: Span) { let ty = self.cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; + let mut visitor = ImproperCTypesVisitor { cx: self.cx, cache: FxHashSet::default() }; let ffi_res = visitor.check_for_type(CTypesVisitorState::StaticTy, ty); self.process_ffi_result(span, ffi_res, CItemKind::Declaration); } @@ -1239,13 +1277,12 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>, ) { - let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; - let visit_state = match fn_mode{ + let mut visitor = ImproperCTypesVisitor { cx: self.cx, cache: FxHashSet::default() }; + let visit_state = match fn_mode { CItemKind::Definition => CTypesVisitorState::ArgumentTyInDefinition, CItemKind::Declaration => CTypesVisitorState::ArgumentTyInDeclaration, }; @@ -1254,8 +1291,8 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let mut visitor = ImproperCTypesVisitor{cx: self.cx, cache: FxHashSet::default()}; - let visit_state = match fn_mode{ + let mut visitor = ImproperCTypesVisitor { cx: self.cx, cache: FxHashSet::default() }; + let visit_state = match fn_mode { CItemKind::Definition => CTypesVisitorState::ReturnTyInDefinition, CItemKind::Declaration => CTypesVisitorState::ReturnTyInDeclaration, }; @@ -1264,13 +1301,7 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ } } - - fn process_ffi_result( - &self, - sp: Span, - res: FfiResult<'tcx>, - fn_mode: CItemKind, - ) { + fn process_ffi_result(&self, sp: Span, res: FfiResult<'tcx>, fn_mode: CItemKind) { match res { FfiResult::FfiSafe => {} FfiResult::FfiPhantom(ty) => { @@ -1287,35 +1318,41 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ fn_mode, ); } - FfiResult::FfiUnsafe(reasons) => for reason in reasons { - let mut ffiresult_recursor = ControlFlow::Continue(&reason); - 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{ - ref ty, ref reason, ref help, ref 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: reason.clone(), - span_note: None, // filled later - }); - - if let Some(inner) = inner { - ffiresult_recursor = ControlFlow::Continue(inner.as_ref()); - } else { - ffiresult_recursor = ControlFlow::Break(()); + FfiResult::FfiUnsafe(reasons) => { + for reason in reasons { + let mut ffiresult_recursor = ControlFlow::Continue(&reason); + 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 { + ref ty, + ref reason, + ref help, + ref 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: reason.clone(), + span_note: None, // filled later + }); + + if let Some(inner) = inner { + ffiresult_recursor = ControlFlow::Continue(inner.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } } + // should always have at least one type + let last_ty = cimproper_layers.last().unwrap().ty.clone(); + self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers, fn_mode); } - // should always have at least one type - let last_ty = cimproper_layers.last().unwrap().ty.clone(); - self.emit_ffi_unsafe_type_lint(last_ty, sp, cimproper_layers, fn_mode); } } } @@ -1347,14 +1384,12 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx>{ self.cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); } - } impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { - let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); - let lint = ImproperCTypesLint{cx}; + let lint = ImproperCTypesLint { cx }; match it.kind { hir::ForeignItemKind::Fn(sig, _, _) => { @@ -1362,7 +1397,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { lint.check_fn_for_external_abi_fnptr( CItemKind::Declaration, it.owner_id.def_id, - sig.decl + sig.decl, ) } else { lint.check_foreign_fn(CItemKind::Declaration, it.owner_id.def_id, sig.decl); @@ -1389,7 +1424,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { hir::ItemKind::Static(ty, ..) | hir::ItemKind::Const(ty, ..) | hir::ItemKind::TyAlias(ty, ..) => { - ImproperCTypesLint{cx}.check_type_for_external_abi_fnptr( + ImproperCTypesLint { cx }.check_type_for_external_abi_fnptr( CItemKind::Definition, ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), @@ -1418,7 +1453,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { } fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { - ImproperCTypesLint{cx}.check_type_for_external_abi_fnptr( + ImproperCTypesLint { cx }.check_type_for_external_abi_fnptr( CItemKind::Definition, field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), @@ -1442,9 +1477,9 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { _ => return, }; - let lint = ImproperCTypesLint{cx}; + let lint = ImproperCTypesLint { cx }; if fn_abi_is_internal(abi) { - lint.check_fn_for_external_abi_fnptr(CItemKind::Definition,id, decl); + lint.check_fn_for_external_abi_fnptr(CItemKind::Definition, id, decl); } else { lint.check_foreign_fn(CItemKind::Definition, id, decl); } diff --git a/tests/ui/abi/nullable-pointer-ffi-compat.rs b/tests/ui/abi/nullable-pointer-ffi-compat.rs index f94f838723a56..33d856732b2c5 100644 --- a/tests/ui/abi/nullable-pointer-ffi-compat.rs +++ b/tests/ui/abi/nullable-pointer-ffi-compat.rs @@ -14,6 +14,7 @@ use std::mem; +#[allow(improper_ctypes_definitions)] // it's worried about invalid pointers given as values of x #[inline(never)] extern "C" fn foo(x: &isize) -> Option<&isize> { Some(x) } diff --git a/tests/ui/asm/issue-97490.rs b/tests/ui/asm/issue-97490.rs index 5f364a22bc437..931c378704a29 100644 --- a/tests/ui/asm/issue-97490.rs +++ b/tests/ui/asm/issue-97490.rs @@ -2,6 +2,8 @@ //@ only-x86_64 //@ needs-asm-support +#[allow(improper_ctypes_definitions)] // it's worried about invalid pointers being given as the + // argument value pub type Yes = extern "sysv64" fn(&'static u8) -> !; 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 b5c718ec38147..abd52584b1e57 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 @@ -5,7 +5,9 @@ LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr` - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes_definitions)]` on by default @@ -16,7 +18,9 @@ LL | fn meh(blah: Foo); | ^^^ not FFI-safe | = note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr` - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` on by default 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..723d0aa3d54c3 100644 --- a/tests/ui/lint/improper_ctypes/repr-rust-is-undefined.stderr +++ b/tests/ui/lint/improper_ctypes/repr-rust-is-undefined.stderr @@ -23,6 +23,12 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn bar(x: B); | ^ not FFI-safe | + = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:13:1 + | +LL | struct B { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -51,6 +57,12 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn quux(x: B2); | ^^ not FFI-safe | + = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:13:1 + | +LL | struct B { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -65,6 +77,12 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn fred(x: D); | ^ not FFI-safe | + = note: this struct/enum/union (`D`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:28:1 + | +LL | struct D { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here diff --git a/tests/ui/lint/lint-ctypes-113436-1.stderr b/tests/ui/lint/lint-ctypes-113436-1.stderr index 7b63043f05756..a4a45de331fa7 100644 --- a/tests/ui/lint/lint-ctypes-113436-1.stderr +++ b/tests/ui/lint/lint-ctypes-113436-1.stderr @@ -4,6 +4,12 @@ error: `extern` fn uses type `NotSafe`, which is not FFI-safe LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-ctypes-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -23,6 +29,12 @@ error: `extern` fn uses type `NotSafe`, which is not FFI-safe LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-ctypes-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here diff --git a/tests/ui/lint/lint-ctypes-73249-3.stderr b/tests/ui/lint/lint-ctypes-73249-3.stderr index e5607ba72e978..99f6dc263633f 100644 --- a/tests/ui/lint/lint-ctypes-73249-3.stderr +++ b/tests/ui/lint/lint-ctypes-73249-3.stderr @@ -4,6 +4,12 @@ error: `extern` block uses type `Qux`, which is not FFI-safe LL | pub fn lint_me() -> A; | ^ not FFI-safe | + = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field +note: the type is defined here + --> $DIR/lint-ctypes-73249-3.rs:15:1 + | +LL | pub struct A { + | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-ctypes-73249-3.rs:2:9 diff --git a/tests/ui/lint/lint-ctypes-cstr.rs b/tests/ui/lint/lint-ctypes-cstr.rs index c4de5a44a9623..aa7afd12ee587 100644 --- a/tests/ui/lint/lint-ctypes-cstr.rs +++ b/tests/ui/lint/lint-ctypes-cstr.rs @@ -6,24 +6,24 @@ 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead fn take_cstr_ref(s: &CStr); //~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` fn take_cstring(s: CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe - //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + //~| HELP consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead fn take_cstring_ref(s: &CString); //~^ ERROR `extern` block uses type `CString`, which is not FFI-safe //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` - fn no_special_help_for_mut_cstring(s: *mut CString); + 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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer - 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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer } extern "C" fn rust_take_cstr_ref(s: &CStr) {} @@ -31,6 +31,10 @@ extern "C" fn rust_take_cstr_ref(s: &CStr) {} //~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` extern "C" fn rust_take_cstring(s: CString) {} //~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe -//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` -extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {} -extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {} +//~| HELP consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead +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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer +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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer diff --git a/tests/ui/lint/lint-ctypes-cstr.stderr b/tests/ui/lint/lint-ctypes-cstr.stderr index da15b748f2110..de6d6592bf3f4 100644 --- a/tests/ui/lint/lint-ctypes-cstr.stderr +++ b/tests/ui/lint/lint-ctypes-cstr.stderr @@ -4,7 +4,9 @@ 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-ctypes-cstr.rs:2:9 @@ -18,8 +20,9 @@ error: `extern` block uses type `&CStr`, which is not FFI-safe 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, and use `CStr::as_ptr()` or `CString::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe --> $DIR/lint-ctypes-cstr.rs:13:24 @@ -27,7 +30,9 @@ 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe @@ -36,29 +41,26 @@ error: `extern` block uses type `CString`, which is not FFI-safe LL | fn take_cstring_ref(s: &CString); | ^^^^^^^^ not FFI-safe | - = note: this reference (`&CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout - = 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, and use `CStr::as_ptr()` or `CString::as_ptr()` = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-ctypes-cstr.rs:20:43 + --> $DIR/lint-ctypes-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 | - = note: this reference (`*mut CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout - = 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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-ctypes-cstr.rs:24:47 + --> $DIR/lint-ctypes-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 | - = note: this reference (`&mut CString`) is ABI-compatible with a C pointer, but `CString` itself does not have a C layout - = 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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` fn uses type `&CStr`, which is not FFI-safe --> $DIR/lint-ctypes-cstr.rs:29:37 @@ -66,8 +68,9 @@ error: `extern` fn uses type `&CStr`, which is not FFI-safe 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, and use `CStr::as_ptr()` or `CString::as_ptr()` + = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-ctypes-cstr.rs:2:26 | @@ -80,8 +83,28 @@ 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead, + and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + (note that `CString::into_raw()`'s output must not be `libc::free()`'d) + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-ctypes-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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer = 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/lint-ctypes-fn.rs b/tests/ui/lint/lint-ctypes-fn.rs index e16ff9573fd18..fa23b7b9c514f 100644 --- a/tests/ui/lint/lint-ctypes-fn.rs +++ b/tests/ui/lint/lint-ctypes-fn.rs @@ -74,8 +74,10 @@ pub extern "C" fn str_type(p: &str) { } //~^ ERROR: uses type `&str` pub extern "C" fn box_type(p: Box) { } +//~^ ERROR: uses type `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]>` @@ -130,11 +132,15 @@ 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]) { } +//~^ ERROR: uses type `&[u8; 4]` pub extern "C" fn good5(s: StructWithProjection) { } -pub extern "C" fn good6(s: StructWithProjectionAndLifetime) { } +pub extern "C" fn argument_with_assumptions_6(s: StructWithProjectionAndLifetime) { } +//~^ ERROR: uses type `&mut StructWithProjectionAndLifetime<'_>` +// note: the type translation might be a little eager for +// `::It` pub extern "C" fn good7(fptr: extern "C" fn() -> ()) { } @@ -150,7 +156,8 @@ 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) { } +//~^ ERROR: uses type `&TransparentInt` pub extern "C" fn good15(p: TransparentLifetime) { } diff --git a/tests/ui/lint/lint-ctypes-fn.stderr b/tests/ui/lint/lint-ctypes-fn.stderr index c86c02c80064c..b5d5426317de4 100644 --- a/tests/ui/lint/lint-ctypes-fn.stderr +++ b/tests/ui/lint/lint-ctypes-fn.stderr @@ -21,8 +21,18 @@ LL | pub extern "C" fn str_type(p: &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` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-ctypes-fn.rs:76:31 + | +LL | pub extern "C" fn box_type(p: Box) { } + | ^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `Box` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:80:34 + --> $DIR/lint-ctypes-fn.rs:82:34 | LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe @@ -31,7 +41,7 @@ LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } = 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-ctypes-fn.rs:83:35 + --> $DIR/lint-ctypes-fn.rs:85:35 | LL | pub extern "C" fn boxed_string(p: Box) { } | ^^^^^^^^ not FFI-safe @@ -40,7 +50,7 @@ LL | pub extern "C" fn boxed_string(p: Box) { } = 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-ctypes-fn.rs:86:34 + --> $DIR/lint-ctypes-fn.rs:88:34 | LL | pub extern "C" fn boxed_trait(p: Box) { } | ^^^^^^^^^^^^^^ not FFI-safe @@ -48,7 +58,7 @@ LL | pub extern "C" fn boxed_trait(p: Box) { } = 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-ctypes-fn.rs:89:32 + --> $DIR/lint-ctypes-fn.rs:91:32 | LL | pub extern "C" fn char_type(p: char) { } | ^^^^ not FFI-safe @@ -57,7 +67,7 @@ LL | pub extern "C" fn char_type(p: char) { } = note: the `char` type has no C equivalent error: `extern` fn uses type `i128`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:92:32 + --> $DIR/lint-ctypes-fn.rs:94:32 | LL | pub extern "C" fn i128_type(p: i128) { } | ^^^^ not FFI-safe @@ -65,7 +75,7 @@ LL | pub extern "C" fn i128_type(p: i128) { } = note: 128-bit integers don't currently have a known stable ABI error: `extern` fn uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:95:32 + --> $DIR/lint-ctypes-fn.rs:97:32 | LL | pub extern "C" fn u128_type(p: u128) { } | ^^^^ not FFI-safe @@ -73,7 +83,7 @@ LL | pub extern "C" fn u128_type(p: u128) { } = note: 128-bit integers don't currently have a known stable ABI error: `extern` fn uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:98:33 + --> $DIR/lint-ctypes-fn.rs:100:33 | LL | pub extern "C" fn tuple_type(p: (i32, i32)) { } | ^^^^^^^^^^ not FFI-safe @@ -82,7 +92,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-ctypes-fn.rs:101:34 + --> $DIR/lint-ctypes-fn.rs:103:34 | LL | pub extern "C" fn tuple_type2(p: I32Pair) { } | ^^^^^^^ not FFI-safe @@ -91,7 +101,7 @@ LL | pub extern "C" fn tuple_type2(p: I32Pair) { } = note: tuples have unspecified layout error: `extern` fn uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:104:32 + --> $DIR/lint-ctypes-fn.rs:106:32 | LL | pub extern "C" fn zero_size(p: ZeroSize) { } | ^^^^^^^^ not FFI-safe @@ -105,7 +115,7 @@ LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:107:40 + --> $DIR/lint-ctypes-fn.rs:109:40 | LL | pub extern "C" fn zero_size_phantom(p: ZeroSizeWithPhantomData) { } | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -118,7 +128,7 @@ LL | pub struct ZeroSizeWithPhantomData(PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:110:51 + --> $DIR/lint-ctypes-fn.rs:112:51 | LL | pub extern "C" fn zero_size_phantom_toplevel() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -126,7 +136,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-ctypes-fn.rs:115:30 + --> $DIR/lint-ctypes-fn.rs:117:30 | LL | pub extern "C" fn fn_type(p: RustFn) { } | ^^^^^^ not FFI-safe @@ -135,7 +145,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-ctypes-fn.rs:118:31 + --> $DIR/lint-ctypes-fn.rs:120:31 | LL | pub extern "C" fn fn_type2(p: fn()) { } | ^^^^ not FFI-safe @@ -144,7 +154,7 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = note: this function pointer has Rust-specific calling convention error: `extern` fn uses type `i128`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:123:39 + --> $DIR/lint-ctypes-fn.rs:125:39 | LL | pub extern "C" fn transparent_i128(p: TransparentI128) { } | ^^^^^^^^^^^^^^^ not FFI-safe @@ -152,7 +162,7 @@ LL | pub extern "C" fn transparent_i128(p: TransparentI128) { } = note: 128-bit integers don't currently have a known stable ABI error: `extern` fn uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:126:38 + --> $DIR/lint-ctypes-fn.rs:128:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe @@ -160,8 +170,44 @@ LL | pub extern "C" fn transparent_str(p: TransparentStr) { } = help: consider using `*const u8` and a length instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +error: `extern` fn uses type `&[u8; 4]`, which is not FFI-safe + --> $DIR/lint-ctypes-fn.rs:135:53 + | +LL | pub extern "C" fn argument_with_assumptions_4(aptr: &[u8; 4 as usize]) { } + | ^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `&[u8; 4]` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + +error: `extern` fn uses type `&mut StructWithProjectionAndLifetime<'_>`, which is not FFI-safe + --> $DIR/lint-ctypes-fn.rs:140:50 + | +LL | pub extern "C" fn argument_with_assumptions_6(s: StructWithProjectionAndLifetime) { } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`StructWithProjectionAndLifetime<'_>`) is FFI-unsafe due to a `&mut StructWithProjectionAndLifetime<'_>` field +note: the type is defined here + --> $DIR/lint-ctypes-fn.rs:18:1 + | +LL | pub struct StructWithProjectionAndLifetime<'a>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using a raw pointer, or wrapping `&mut StructWithProjectionAndLifetime<'_>` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + +error: `extern` fn uses type `&TransparentInt`, which is not FFI-safe + --> $DIR/lint-ctypes-fn.rs:159:51 + | +LL | pub extern "C" fn argument_with_assumptions_14(p: TransparentRef) { } + | ^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `&TransparentInt` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:172:43 + --> $DIR/lint-ctypes-fn.rs:179:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -169,7 +215,7 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:185:39 + --> $DIR/lint-ctypes-fn.rs:192:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -178,7 +224,7 @@ LL | pub extern "C" fn used_generic4(x: Vec) { } = note: this struct has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-ctypes-fn.rs:188:41 + --> $DIR/lint-ctypes-fn.rs:195:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe @@ -186,5 +232,5 @@ LL | pub extern "C" fn used_generic5() -> Vec { = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout -error: aborting due to 20 previous errors +error: aborting due to 24 previous errors diff --git a/tests/ui/lint/lint-ctypes-tykind-fuzz.rs b/tests/ui/lint/lint-ctypes-tykind-fuzz.rs index 27bcfcf0e9a0c..6bbb848762eba 100644 --- a/tests/ui/lint/lint-ctypes-tykind-fuzz.rs +++ b/tests/ui/lint/lint-ctypes-tykind-fuzz.rs @@ -47,10 +47,10 @@ impl TemplateStruct { extern "C" fn tstruct_sum( // Ref[Struct] - slf: &TemplateStruct - // Alias ...not Inherent. dangit -) -> TemplateStruct::Out { - slf.one + slf.two + slf: Option<&TemplateStruct> + // Option> ...not Inherent. dangit +) -> Option::Out>> { + Some(Box::new(slf?.one + slf?.two)) } #[repr(C)] @@ -87,7 +87,7 @@ pub trait TimesTwo: std::ops::Add + Sized + Clone //} extern "C" fn t2_box( // Box[Param] - self: Box + self: Box, // Alias ) -> as std::ops::Add>>::Output { self.clone() + self @@ -161,36 +161,35 @@ extern "C" fn all_ty_kinds<'a,const N:usize,T>( 3_usize } -extern "C" { -fn all_ty_kinds_in_ptr( +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, //~ ERROR: uses type `String` + s: *const String, // Ptr[Str] s2: *const str, //~ ERROR: uses type `*const str` // Ptr[Char] - c: *const char, //~ ERROR: uses type `char` + c: *const char, // Ptr[Slice] s3: *const [u8], //~ ERROR: uses type `*const [u8]` - // deactivated here, because this is a function *declaration* (param N unacceptable) - // s4: *const [u8;N], + // Ptr[Array] (this gets caught outside of the code we want to test) + s4: *const [u8;N], // Ptr[Tuple] - p: *const (u8,u8), //~ ERROR: uses type `(u8, u8)` - // deactivated here, because this is a function *declaration* (pattern unacceptable) - // (p2, p3):(*const u8, *const u8), + p: *const (u8,u8), + // Tuple + (p2, p3):(*const u8, *const u8), // Pat nz: *const pattern_type!(u32 is 1..), //~ ERROR: uses type `(u32) is 1..=` - // deactivated here, because this is a function *declaration* (pattern unacceptable) - //SomeStruct{b: ref p4,..}: & SomeStruct, + // Ptr[Struct] + SomeStruct{b: ref p4,..}: & SomeStruct, // Ptr[Union] u2: *const SomeUnion, // Ptr[Enum], e: *const SomeEnum, - // deactivated here, because this is a function *declaration* (impl type unacceptable) - //d: *const impl Clone, - // deactivated here, because this is a function *declaration* (type param unacceptable) - //t: *const T, + // Param + d: *const impl Clone, + // Param + t: *const T, // Ptr[Foreign] e2: *mut ExtType, // Ptr[Struct] @@ -199,46 +198,48 @@ fn all_ty_kinds_in_ptr( x: *const !, //r1: &u8, r2: *const u8, r3: Box, // Ptr[FnPtr] - f2: *const fn(u8)->u8, //~ ERROR: uses type `fn(u8) -> u8` + 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` - // deactivated here, because this is a function *declaration* (impl type unacceptable) - //a: *const impl async Fn(u8)->u8, + // Ptr[Param], + a: *const impl async Fn(u8)->u8, // Alias (this gets caught outside of the code we want to test) -) -> *const dyn std::fmt::Debug; //~ ERROR: uses type `*const dyn Debug` +) -> *const dyn std::fmt::Debug { //~ ERROR: uses type `*const dyn Debug` + todo!() } -extern "C" fn all_ty_kinds_in_ref<'a, const N:usize,T>( +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, + s: &String, //~ ERROR: uses type `String` // Ref[Str] s2: &str, //~ ERROR: uses type `&str` // Ref[Char] - c: &char, + c: &char, //~ ERROR: uses type `char` // Ref[Slice] s3: &[u8], //~ ERROR: uses type `&[u8]` - // Ref[Array] (this gets caught outside of the code we want to test) - s4: &[u8;N], + // deactivated here, because this is a function *declaration* (param N unacceptable) + // s4: &[u8;N], // Ref[Tuple] - p: &(u8, u8), - // also Tuple - (p2, p3):(&u8, &u8), //~ ERROR: uses type `(&u8, &u8)` + p: &(u8, u8), //~ ERROR: uses type `(u8, u8)` + // deactivated here, because this is a function *declaration* (patterns unacceptable) + // (p2, p3):(&u8, &u8), //~ ERROR: uses type `(&u8, &u8)` // Pat nz: &pattern_type!(u32 is 1..), - // Ref[Struct] - SomeStruct{b: ref p4,..}: &SomeStruct, + // deactivated here, because this is a function *declaration* (pattern unacceptable) + // SomeStruct{b: ref p4,..}: &SomeStruct, // Ref[Union] u2: &SomeUnion, // Ref[Enum], e: &SomeEnum, - // Ref[Param] - d: &impl Clone, - // Ref[Param] - t: &T, + // 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] @@ -247,65 +248,65 @@ extern "C" fn all_ty_kinds_in_ref<'a, const N:usize,T>( x: &!, //r1: &u8, r2: &u8, r3: Box, // Ref[FnPtr] - f2: &fn(u8)->u8, + f2: &fn(u8)->u8, //~ ERROR: uses type `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` - // Ref[Param], - a: &impl async Fn(u8)->u8, + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // a: &impl async Fn(u8)->u8, // Ref[Dynamic] (this gets caught outside of the code we want to test) -) -> &'a dyn std::fmt::Debug { //~ ERROR: uses type `&dyn Debug` - i +) -> &'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: Box, i: Box, f: Box, b: Box, + u: Option>, i: Option>, f: Option>, b: Option>, // Box[Struct] - s: Box, + s: Option>, // Box[Str] s2: Box, //~ ERROR: uses type `Box` // Box[Char] - c: Box, + c: Box, //~ ERROR: uses type `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: Box<[u8;N]>, + s4: Option>, // Box[Tuple] - p: Box<(u8,u8)>, + p: Option>, // also Tuple (p2,p3):(Box, Box), //~ ERROR: uses type `(Box, Box)` // Pat - nz: Box, + nz: Option>, // Ref[Struct] SomeStruct{b: ref p4,..}: &SomeStruct, // Box[Union] - u2: Box, + u2: Option>, // Box[Enum], - e: Box, + e: Option>, // Box[Param] - d: Box, + d: Option>, // Box[Param] - t: Box, + t: Option>, // Box[Foreign] - e2: Box, + e2: Option>, // Box[Struct] e3: Box, //~ ERROR: uses type `Box` // Box[Never] - x: Box, + // (considered FFI-unsafe because of null pointers, not the litteral uninhabited type. smh.) + x: Box, //~ ERROR: uses type `Box` //r1: Box, // Box[FnPtr] - f2: Boxu8>, + f2: Boxu8>, //~ ERROR: uses type `Box u8>` // Box[Dynamic] f3: Boxu8>, //~ ERROR: uses type `Box u8>` // Box[Dynamic] d2: Box>, //~ ERROR: uses type `Box>` - // Box[Param], - a: Boxu8>, + // Option[Box[Param]], + a: Optionu8>>, // Box[Dynamic] (this gets caught outside of the code we want to test) ) -> Box { //~ ERROR: uses type `Box` - i + u.unwrap() } fn main() {} diff --git a/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr b/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr index e807d76bd5264..e2425349b47a2 100644 --- a/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr +++ b/tests/ui/lint/lint-ctypes-tykind-fuzz.stderr @@ -82,7 +82,7 @@ LL | nz: pattern_type!(u32 is 1..), | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = help: consider using the base type instead - = note: pattern types have no C equivalent + = note: integers constrained to a given range cannot have their value be provided by non-rust code error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe --> $DIR/lint-ctypes-tykind-fuzz.rs:147:7 @@ -125,23 +125,8 @@ LL | ) -> impl std::fmt::Debug { | = note: opaque types have no C equivalent -error: `extern` block uses type `String`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:169:6 - | -LL | s: *const String, - | ^^^^^^^^^^^^^ not FFI-safe - | - = note: this reference (`*const String`) is ABI-compatible with a C pointer, but `String` itself does not have a C layout - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout -note: the lint level is defined here - --> $DIR/lint-ctypes-tykind-fuzz.rs:5:9 - | -LL | #![deny(improper_ctypes,improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^ - -error: `extern` block uses type `*const str`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:171:7 +error: `extern` fn uses type `*const str`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:170:7 | LL | s2: *const str, | ^^^^^^^^^^ not FFI-safe @@ -149,18 +134,8 @@ LL | s2: *const str, = 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` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:173:6 - | -LL | c: *const char, - | ^^^^^^^^^^^^ not FFI-safe - | - = note: this reference (`*const char`) is ABI-compatible with a C pointer, but `char` itself does not have a C layout - = help: consider using `u32` or `libc::wchar_t` instead - = note: the `char` type has no C equivalent - -error: `extern` block uses type `*const [u8]`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:175:7 +error: `extern` fn uses type `*const [u8]`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:174:7 | LL | s3: *const [u8], | ^^^^^^^^^^^ not FFI-safe @@ -168,70 +143,74 @@ LL | s3: *const [u8], = 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` block uses type `(u8, u8)`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:179:6 +error: `extern` fn uses type `(*const u8, *const u8)`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:180:12 | -LL | p: *const (u8,u8), - | ^^^^^^^^^^^^^^ not FFI-safe +LL | (p2, p3):(*const u8, *const u8), + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = note: this reference (`*const (u8, u8)`) is ABI-compatible with a C pointer, but `(u8, u8)` itself does not have a C layout = help: consider using a struct instead = note: tuples have unspecified layout -error: `extern` block uses type `(u32) is 1..=`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:183:7 +error: `extern` fn uses type `&SomeStruct`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:184:29 | -LL | nz: *const pattern_type!(u32 is 1..), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe +LL | SomeStruct{b: ref p4,..}: & SomeStruct, + | ^^^^^^^^^^^^ not FFI-safe | - = note: this reference (`*const (u32) is 1..=`) is ABI-compatible with a C pointer, but `(u32) is 1..=` itself does not have a C layout - = help: consider using the base type instead - = note: pattern types have no C equivalent + = help: consider using a raw pointer, or wrapping `&SomeStruct` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code -error: `extern` block uses type `*const StructWithDyn`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:197:7 +error: `extern` fn uses type `*const StructWithDyn`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:196:7 | 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` block uses type `fn(u8) -> u8`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:202:7 - | -LL | f2: *const fn(u8)->u8, - | ^^^^^^^^^^^^^^^^^ not FFI-safe - | - = note: this reference (`*const fn(u8) -> u8`) is ABI-compatible with a C pointer, but `fn(u8) -> u8` itself does not have a C layout - = help: consider using an `extern fn(...) -> ...` function pointer instead - = note: this function pointer has Rust-specific calling convention - -error: `extern` block uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:204:7 +error: `extern` fn uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:203:7 | 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` block uses type `*const dyn PartialOrd`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:206:7 +error: `extern` fn uses type `*const dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:205:7 | 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` block uses type `*const dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:210:6 +error: `extern` fn uses type `*const dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:209:6 | -LL | ) -> *const dyn std::fmt::Debug; +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` fn uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:219:7 +error: `extern` block uses type `String`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:218:6 + | +LL | s: &String, + | ^^^^^^^ not FFI-safe + | + = note: this reference (`&String`) is ABI-compatible with a C pointer, but `String` itself does not have a C layout + = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct + = note: this struct has unspecified layout +note: the lint level is defined here + --> $DIR/lint-ctypes-tykind-fuzz.rs:5:9 + | +LL | #![deny(improper_ctypes,improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:220:7 | LL | s2: &str, | ^^^^ not FFI-safe @@ -239,8 +218,18 @@ LL | s2: &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` fn uses type `&[u8]`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:223:7 +error: `extern` block uses type `char`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:222:6 + | +LL | c: &char, + | ^^^^^ not FFI-safe + | + = note: this reference (`&char`) is ABI-compatible with a C pointer, but `char` itself does not have a C layout + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:224:7 | LL | s3: &[u8], | ^^^^^ not FFI-safe @@ -248,43 +237,54 @@ LL | s3: &[u8], = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer -error: `extern` fn uses type `(&u8, &u8)`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:229:12 +error: `extern` block uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:228:6 | -LL | (p2, p3):(&u8, &u8), - | ^^^^^^^^^^ not FFI-safe +LL | p: &(u8, u8), + | ^^^^^^^^^ not FFI-safe | + = note: this reference (`&(u8, u8)`) is ABI-compatible with a C pointer, but `(u8, u8)` itself does not have a C layout = help: consider using a struct instead = note: tuples have unspecified layout -error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:245:7 +error: `extern` block uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:246:7 | 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 `&dyn Fn(u8) -> u8`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:252:7 +error: `extern` block uses type `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:251:7 + | +LL | f2: &fn(u8)->u8, + | ^^^^^^^^^^^ not FFI-safe + | + = note: this reference (`&fn(u8) -> u8`) is ABI-compatible with a C pointer, but `fn(u8) -> u8` itself does not have a C layout + = help: consider using an `extern fn(...) -> ...` function pointer instead + = note: this function pointer has Rust-specific calling convention + +error: `extern` block uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:253:7 | 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` fn uses type `&dyn PartialOrd`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:254:7 +error: `extern` block uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:255:7 | 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 `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:258:6 +error: `extern` block uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:259:6 | -LL | ) -> &'a dyn std::fmt::Debug { +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 @@ -298,6 +298,15 @@ LL | s2: Box, = 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-ctypes-tykind-fuzz.rs:270:6 + | +LL | c: Box, + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `Box` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe --> $DIR/lint-ctypes-tykind-fuzz.rs:272:7 | @@ -316,6 +325,16 @@ LL | (p2,p3):(Box, Box), = help: consider using a struct instead = note: tuples have unspecified layout +error: `extern` fn uses type `&SomeStruct`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:282:29 + | +LL | SomeStruct{b: ref p4,..}: &SomeStruct, + | ^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `&SomeStruct` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + error: `extern` fn uses type `Box`, which is not FFI-safe --> $DIR/lint-ctypes-tykind-fuzz.rs:294:7 | @@ -324,8 +343,27 @@ LL | e3: Box, | = 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-ctypes-tykind-fuzz.rs:297:6 + | +LL | x: Box, + | ^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `Box` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code +error: `extern` fn uses type `Box u8>`, which is not FFI-safe + --> $DIR/lint-ctypes-tykind-fuzz.rs:300:7 + | +LL | f2: Boxu8>, + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer, or wrapping `Box u8>` in an `Option<_>` + = note: boxes and references are assumed to be valid (non-null, non-dangling, aligned) pointers, + which cannot be garanteed if their values are produced by non-rust code + error: `extern` fn uses type `Box u8>`, which is not FFI-safe - --> $DIR/lint-ctypes-tykind-fuzz.rs:301:7 + --> $DIR/lint-ctypes-tykind-fuzz.rs:302:7 | LL | f3: Boxu8>, | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -333,7 +371,7 @@ LL | f3: Boxu8>, = 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-ctypes-tykind-fuzz.rs:303:7 + --> $DIR/lint-ctypes-tykind-fuzz.rs:304:7 | LL | d2: Box>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -341,12 +379,12 @@ LL | d2: Box>, = 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-ctypes-tykind-fuzz.rs:307:6 + --> $DIR/lint-ctypes-tykind-fuzz.rs:308:6 | LL | ) -> Box { | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer -error: aborting due to 38 previous errors; 1 warning emitted +error: aborting due to 42 previous errors; 1 warning emitted diff --git a/tests/ui/lint/lint-ctypes.rs b/tests/ui/lint/lint-ctypes.rs index b9b401e4e4cde..dcdebb80594f4 100644 --- a/tests/ui/lint/lint-ctypes.rs +++ b/tests/ui/lint/lint-ctypes.rs @@ -1,5 +1,7 @@ #![feature(rustc_private)] #![feature(extern_types)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -8,6 +10,7 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; use std::fmt::Debug; +use std::pat::pattern_type; unsafe extern "C" {type UnsizedOpaque;} trait Bar { } @@ -74,6 +77,8 @@ extern "C" { pub fn char_type(p: char); //~ ERROR uses type `char` pub fn i128_type(p: i128); //~ ERROR uses type `i128` pub fn u128_type(p: u128); //~ ERROR uses type `u128` + pub fn pat_type1() -> pattern_type!(u32 is 1..); //~ ERROR uses type `(u32) is 1..=` + pub fn pat_type2(p: pattern_type!(u32 is 1..)); // no error! pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `&dyn Bar` pub fn tuple_type(p: (i32, i32)); //~ ERROR uses type `(i32, i32)` pub fn tuple_type2(p: I32Pair); //~ ERROR uses type `(i32, i32)` diff --git a/tests/ui/lint/lint-ctypes.stderr b/tests/ui/lint/lint-ctypes.stderr index 9ec17b1d94b32..35fb5ee2970fd 100644 --- a/tests/ui/lint/lint-ctypes.stderr +++ b/tests/ui/lint/lint-ctypes.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:60:28 + --> $DIR/lint-ctypes.rs:69:28 | LL | pub fn ptr_type1(size: *const Foo); | ^^^^^^^^^^ not FFI-safe @@ -8,18 +8,18 @@ LL | pub fn ptr_type1(size: *const Foo); = 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:28:1 + --> $DIR/lint-ctypes.rs:31:1 | LL | pub struct Foo; | ^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint-ctypes.rs:5:9 + --> $DIR/lint-ctypes.rs:7:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:61:28 + --> $DIR/lint-ctypes.rs:70:28 | LL | pub fn ptr_type2(size: *const Foo); | ^^^^^^^^^^ not FFI-safe @@ -28,13 +28,13 @@ LL | pub fn ptr_type2(size: *const Foo); = 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:28:1 + --> $DIR/lint-ctypes.rs:31:1 | LL | pub struct Foo; | ^^^^^^^^^^^^^^ error: `extern` block uses type `((),)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:63:25 + --> $DIR/lint-ctypes.rs:72:25 | LL | pub fn ptr_tuple(p: *const ((),)); | ^^^^^^^^^^^^ not FFI-safe @@ -44,7 +44,7 @@ LL | pub fn ptr_tuple(p: *const ((),)); = note: tuples have unspecified layout error: `extern` block uses type `&[u32]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:64:26 + --> $DIR/lint-ctypes.rs:73:26 | LL | pub fn slice_type(p: &[u32]); | ^^^^^^ not FFI-safe @@ -53,7 +53,7 @@ LL | pub fn slice_type(p: &[u32]); = note: this reference 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-ctypes.rs:65:24 + --> $DIR/lint-ctypes.rs:74:24 | LL | pub fn str_type(p: &str); | ^^^^ not FFI-safe @@ -62,7 +62,7 @@ LL | pub fn str_type(p: &str); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:68:25 + --> $DIR/lint-ctypes.rs:77:25 | LL | pub fn char_type(p: char); | ^^^^ not FFI-safe @@ -71,7 +71,7 @@ LL | pub fn char_type(p: char); = note: the `char` type has no C equivalent error: `extern` block uses type `i128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:69:25 + --> $DIR/lint-ctypes.rs:78:25 | LL | pub fn i128_type(p: i128); | ^^^^ not FFI-safe @@ -79,15 +79,24 @@ LL | pub fn i128_type(p: i128); = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:70:25 + --> $DIR/lint-ctypes.rs:79:25 | LL | pub fn u128_type(p: u128); | ^^^^ not FFI-safe | = note: 128-bit integers don't currently have a known stable ABI +error: `extern` block uses type `(u32) is 1..=`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:80:27 + | +LL | pub fn pat_type1() -> pattern_type!(u32 is 1..); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using the base type instead + = note: integers constrained to a given range cannot have their value be provided by non-rust code + error: `extern` block uses type `&dyn Bar`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:26 + --> $DIR/lint-ctypes.rs:82:26 | LL | pub fn trait_type(p: &dyn Bar); | ^^^^^^^^ not FFI-safe @@ -95,7 +104,7 @@ LL | pub fn trait_type(p: &dyn Bar); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:72:26 + --> $DIR/lint-ctypes.rs:83:26 | LL | pub fn tuple_type(p: (i32, i32)); | ^^^^^^^^^^ not FFI-safe @@ -104,7 +113,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:73:27 + --> $DIR/lint-ctypes.rs:84:27 | LL | pub fn tuple_type2(p: I32Pair); | ^^^^^^^ not FFI-safe @@ -113,7 +122,7 @@ LL | pub fn tuple_type2(p: I32Pair); = note: tuples have unspecified layout error: `extern` block uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:74:25 + --> $DIR/lint-ctypes.rs:85:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -121,26 +130,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: this struct has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:24:1 + --> $DIR/lint-ctypes.rs:27:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:75:33 + --> $DIR/lint-ctypes.rs:86: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:57: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:78:12 + --> $DIR/lint-ctypes.rs:89:12 | LL | -> ::std::marker::PhantomData; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -148,7 +157,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:79:23 + --> $DIR/lint-ctypes.rs:90:23 | LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe @@ -157,7 +166,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:80:24 + --> $DIR/lint-ctypes.rs:91:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -166,7 +175,7 @@ LL | pub fn fn_type2(p: fn()); = note: this function pointer has Rust-specific calling convention error: `extern` block uses type `i128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:82:32 + --> $DIR/lint-ctypes.rs:93:32 | LL | pub fn transparent_i128(p: TransparentI128); | ^^^^^^^^^^^^^^^ not FFI-safe @@ -174,7 +183,7 @@ LL | pub fn transparent_i128(p: TransparentI128); = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:83:31 + --> $DIR/lint-ctypes.rs:94:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe @@ -183,7 +192,7 @@ LL | pub fn transparent_str(p: TransparentStr); = 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:85:27 + --> $DIR/lint-ctypes.rs:96:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -191,8 +200,44 @@ LL | pub fn raw_array(arr: [u8; 8]); = help: consider passing a pointer to the array = note: passing raw arrays by value is not FFI-safe +error: `extern` block uses type `char`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:97:36 + | +LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `char` + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` block uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:97:36 + | +LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `&dyn Debug` + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:97:36 + | +LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `Box>` + = note: this reference (`Box>`) is ABI-compatible with a C pointer, but `TwoBadTypes<'_>` itself does not have a C layout + = 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:88:47 + --> $DIR/lint-ctypes.rs:104:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -200,7 +245,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:26 + --> $DIR/lint-ctypes.rs:106:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -209,7 +254,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:92:26 + --> $DIR/lint-ctypes.rs:108:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -218,7 +263,7 @@ LL | pub fn no_niche_b(b: Option>); = note: enum has no representation hint error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:95:34 + --> $DIR/lint-ctypes.rs:111:34 | LL | pub static static_u128_type: u128; | ^^^^ not FFI-safe @@ -226,12 +271,12 @@ LL | pub static static_u128_type: u128; = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:96:40 + --> $DIR/lint-ctypes.rs:112:40 | LL | pub static static_u128_array_type: [u128; 16]; | ^^^^^^^^^^ not FFI-safe | = note: 128-bit integers don't currently have a known stable ABI -error: aborting due to 24 previous errors +error: aborting due to 28 previous errors From 13720e7eec5188631618ab15aac8144ae2cf3a8a Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sat, 18 Jan 2025 21:17:39 +0100 Subject: [PATCH 9/9] chore: make tidy happy --- compiler/rustc_lint/messages.ftl | 6 ++-- .../rustc_lint/src/types/improper_ctypes.rs | 20 +++++-------- tests/ui/lint/lint-ctypes.rs | 10 ++++--- tests/ui/lint/lint-ctypes.stderr | 28 +++++++++---------- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 2af981d5ee214..2b1e2a702f283 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -411,13 +411,13 @@ lint_improper_ctypes_ptr_validity_reason = lint_improper_ctypes_sized_ptr_to_unsafe_type = this reference (`{$ty}`) is ABI-compatible with a C pointer, but `{$inner_ty}` itself does not have a C layout -lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field 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_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 = this struct has no fields diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index f05906488d575..8f40bc4794bc4 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -410,7 +410,7 @@ impl CTypesVisitorState { fn value_may_be_unchecked(self) -> bool { // function declarations are assumed to be rust-caller, non-rust-callee // function definitions are assumed to be maybe-not-rust-caller, rust-callee - // FnPtrs are... well, nothing's certain about anything. (TODO need more flags in enum?) + // FnPtrs are... well, nothing's certain about anything. (FIXME need more flags in enum?) // Same with statics. if self.is_in_static() { true @@ -771,7 +771,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if def.variants().is_empty() { // Empty enums are implicitely handled as the never type: - // TODO think about the FFI-safety of functions that use that + // FIXME think about the FFI-safety of functions that use that return FfiSafe; } // Check for a repr() attribute to specify the size of the @@ -821,7 +821,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .iter() .map(|variant| { self.visit_variant_fields(state, ty, def, variant, args) - // TODO: check that enums allow any (up to all) variants to be phantoms? + // FIXME: 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?) .forbid_phantom() }) @@ -1117,10 +1117,7 @@ struct ImproperCTypesLint<'c, 'tcx> { } impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx> { - fn check_arg_for_power_alignment( - &mut self, - ty: Ty<'tcx>, - ) -> bool { + fn check_arg_for_power_alignment(&mut self, ty: Ty<'tcx>) -> bool { // 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 @@ -1150,10 +1147,7 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx> { return false; } - fn check_struct_for_power_alignment( - &mut self, - item: &'tcx hir::Item<'tcx>, - ) { + fn check_struct_for_power_alignment(&mut self, item: &'tcx hir::Item<'tcx>) { let tcx = self.cx.tcx; let adt_def = tcx.adt_def(item.owner_id.to_def_id()); if adt_def.repr().c() @@ -1237,7 +1231,7 @@ impl<'c, 'tcx> ImproperCTypesLint<'c, 'tcx> { (span, ffi_res) }) // even in function *definitions*, `FnPtr`s are always function declarations ...right? - // (TODO: we can't do that yet because one of rustc's crates can't compile if we do) + // (FIXME: we can't do that yet because one of rustc's crates can't compile if we do) .for_each(|(span, ffi_res)| self.process_ffi_result(span, ffi_res, fn_mode)); //.drain(); } @@ -1435,7 +1429,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { // Structs are checked based on if they follow the power alignment // rule (under repr(C)). hir::ItemKind::Struct(..) => { - ImproperCTypesLint{cx}.check_struct_for_power_alignment(item); + ImproperCTypesLint { cx }.check_struct_for_power_alignment(item); } // See `check_field_def`.. hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} diff --git a/tests/ui/lint/lint-ctypes.rs b/tests/ui/lint/lint-ctypes.rs index dcdebb80594f4..e06d0721d5a28 100644 --- a/tests/ui/lint/lint-ctypes.rs +++ b/tests/ui/lint/lint-ctypes.rs @@ -94,11 +94,13 @@ extern "C" { pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` - pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); - //~^ ERROR: uses type `char` - //~^^ ERROR: uses type `&dyn Debug` + pub fn multi_errors_per_arg( + f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>) + ); + //~^^ ERROR: uses type `char` + //~^^^ ERROR: uses type `&dyn Debug` // (possible FIXME: the in-struct `char` field doesn't get a warning due ^^) - //~^^^^ ERROR: uses type `&[u8]` + //~^^^^^ ERROR: uses type `&[u8]` pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` diff --git a/tests/ui/lint/lint-ctypes.stderr b/tests/ui/lint/lint-ctypes.stderr index 35fb5ee2970fd..41948c4178c91 100644 --- a/tests/ui/lint/lint-ctypes.stderr +++ b/tests/ui/lint/lint-ctypes.stderr @@ -201,29 +201,29 @@ LL | pub fn raw_array(arr: [u8; 8]); = note: passing raw arrays by value is not FFI-safe error: `extern` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:97:36 + --> $DIR/lint-ctypes.rs:98:12 | -LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `char` = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent error: `extern` block uses type `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:97:36 + --> $DIR/lint-ctypes.rs:98:12 | -LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `&dyn Debug` = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&[u8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:97:36 + --> $DIR/lint-ctypes.rs:98:12 | -LL | pub fn multi_errors_per_arg(f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: Box>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), Box>)` is FFI-unsafe due to `Box>` = note: this reference (`Box>`) is ABI-compatible with a C pointer, but `TwoBadTypes<'_>` itself does not have a C layout @@ -237,7 +237,7 @@ LL | pub struct TwoBadTypes<'a> { = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:104:47 + --> $DIR/lint-ctypes.rs:106:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -245,7 +245,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:106:26 + --> $DIR/lint-ctypes.rs:108:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -254,7 +254,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:108:26 + --> $DIR/lint-ctypes.rs:110:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -263,7 +263,7 @@ LL | pub fn no_niche_b(b: Option>); = note: enum has no representation hint error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:111:34 + --> $DIR/lint-ctypes.rs:113:34 | LL | pub static static_u128_type: u128; | ^^^^ not FFI-safe @@ -271,7 +271,7 @@ LL | pub static static_u128_type: u128; = note: 128-bit integers don't currently have a known stable ABI error: `extern` block uses type `u128`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:112:40 + --> $DIR/lint-ctypes.rs:114:40 | LL | pub static static_u128_array_type: [u128; 16]; | ^^^^^^^^^^ not FFI-safe