about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-12-08 14:28:22 +0100
committerGitHub <noreply@github.com>2024-12-08 14:28:22 +0100
commit703bb982303ecab02fec593899639b4c3faecddd (patch)
treee3e5108b482ab266bbf6643f06d44b316caeee59
parentf415c07494b98e4559e4b13a9c5f867b0e6b2444 (diff)
parent02072fd83ada1ad690057b28bf01c33fb494378d (diff)
downloadrust-703bb982303ecab02fec593899639b4c3faecddd.tar.gz
rust-703bb982303ecab02fec593899639b4c3faecddd.zip
Rollup merge of #131669 - niacdoial:linting-ptrdyn-ffi, r=workingjubilee
lint: change help for pointers to dyn types in FFI

### Context
while playing around, I encountered the warning for dyn types in `extern "C"` functions, but even after that I assumed that a (rust) raw pointer could be interpreted in C ('s ABI) as a `void *`... to be fair part of why I ignored the warning is because I wanted to poke at the generated assembly, not make useful code.

### Example

```rust
extern "C"
fn caller(callee: *const dyn Fn(i32)->i32){
    // -- snip --
}
```

old warning:
```
warning: `extern` fn uses type `dyn Fn(i32) -> i32`, which is not FFI-safe
 --> file/name.rs:42:19
   |
42 | fn caller(callee: *const dyn Fn(i32)->i32) {
   |                   ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
   |
  = note: trait objects have no C equivalent
  = note: `#[warn(improper_ctypes_definitions)]` on by default
  ```

new warning:
```
warning: `extern` fn uses type `dyn Fn(i32) -> i32`, which is not FFI-safe
 --> file/name.rs:42:19
   |
42 | fn caller(callee: *const dyn Fn(i32)->i32) -> (){
   |                   ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
   |
  = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
  = note: `#[warn(improper_ctypes_definitions)]` on by default
```
-rw-r--r--compiler/rustc_lint/messages.ftl13
-rw-r--r--compiler/rustc_lint/src/lints.rs45
-rw-r--r--compiler/rustc_lint/src/types.rs372
-rw-r--r--tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr2
-rw-r--r--tests/ui/extern/extern-C-str-arg-ice-80125.stderr2
-rw-r--r--tests/ui/lint/extern-C-fnptr-lints-slices.rs2
-rw-r--r--tests/ui/lint/extern-C-fnptr-lints-slices.stderr7
-rw-r--r--tests/ui/lint/lint-ctypes-73249-2.stderr1
-rw-r--r--tests/ui/lint/lint-ctypes-94223.stderr26
-rw-r--r--tests/ui/lint/lint-ctypes-cstr.rs4
-rw-r--r--tests/ui/lint/lint-ctypes-cstr.stderr11
-rw-r--r--tests/ui/lint/lint-ctypes-fn.rs6
-rw-r--r--tests/ui/lint/lint-ctypes-fn.stderr22
-rw-r--r--tests/ui/lint/lint-ctypes.rs35
-rw-r--r--tests/ui/lint/lint-ctypes.stderr123
15 files changed, 490 insertions, 181 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index 49e6b763590..422629cd11d 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -359,7 +359,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
 
@@ -377,7 +376,9 @@ lint_improper_ctypes_enum_repr_help =
 lint_improper_ctypes_enum_repr_reason = enum has no representation hint
 lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead
 
+lint_improper_ctypes_fnptr_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}`
 lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention
+
 lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive
 lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants
 
@@ -388,7 +389,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
@@ -414,6 +419,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 20822f23bf1..9fa263799eb 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -1851,13 +1851,44 @@ pub(crate) struct UnpredictableFunctionPointerComparisonsSuggestion<'a> {
     pub right: Span,
 }
 
+pub(crate) struct ImproperCTypesLayer<'a> {
+    pub ty: Ty<'a>,
+    pub inner_ty: Option<Ty<'a>>,
+    pub note: DiagMessage,
+    pub span_note: Option<Span>,
+    pub help: Option<DiagMessage>,
+}
+
+impl<'a> Subdiagnostic for ImproperCTypesLayer<'a> {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        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<DiagMessage>,
-    pub note: DiagMessage,
-    pub span_note: Option<Span>,
+    pub reasons: Vec<ImproperCTypesLayer<'a>>,
 }
 
 // Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
@@ -1867,12 +1898,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 33650be056d..90d44371ab5 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -22,10 +22,10 @@ mod improper_ctypes;
 use crate::lints::{
     AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
     AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
-    AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
-    InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
-    UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons,
-    VariantSizeDifferencesDiag,
+    AtomicOrderingStore, ImproperCTypes, ImproperCTypesLayer, InvalidAtomicOrderingDiag,
+    InvalidNanComparisons, InvalidNanComparisonsSuggestion,
+    UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion,
+    UnusedComparisons, VariantSizeDifferencesDiag,
 };
 use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
 
@@ -727,7 +727,109 @@ struct CTypesVisitorState<'tcx> {
 enum FfiResult<'tcx> {
     FfiSafe,
     FfiPhantom(Ty<'tcx>),
-    FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
+    FfiUnsafe {
+        ty: Ty<'tcx>,
+        reason: DiagMessage,
+        help: Option<DiagMessage>,
+    },
+    FfiUnsafeWrapper {
+        ty: Ty<'tcx>,
+        reason: DiagMessage,
+        help: Option<DiagMessage>,
+        wrapped: Box<FfiResult<'tcx>>,
+    },
+}
+
+/// 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>(
@@ -764,7 +866,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);
 
@@ -933,12 +1035,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
@@ -995,9 +1098,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,
             }
         }
 
@@ -1031,16 +1134,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,
                         };
                     }
                 }
@@ -1196,15 +1330,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(),
@@ -1214,7 +1339,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),
 
@@ -1232,7 +1420,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),
+                            };
+                        }
                     }
                 }
 
@@ -1241,7 +1436,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,
@@ -1278,8 +1481,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         &mut self,
         ty: Ty<'tcx>,
         sp: Span,
-        note: DiagMessage,
-        help: Option<DiagMessage>,
+        mut reasons: Vec<ImproperCTypesLayer<'tcx>>,
     ) {
         let lint = match self.mode {
             CItemKind::Declaration => IMPROPER_CTYPES,
@@ -1289,21 +1491,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 {
@@ -1332,7 +1530,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
@@ -1371,15 +1575,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<ImproperCTypesLayer<'tcx>> = 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 044c1ae2dd4..b5c718ec381 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 ebd6cec6ecd..f2ee21c3166 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 0c35eb37a48..4e3832ab1b6 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 d13f93ca96f..c0923dd96c8 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 ef30a406969..f035cdb213e 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 bd127cf6004..4bebca69b7f 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<extern "C" fn([u8]), 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
@@ -27,7 +29,8 @@ error: `extern` fn uses type `[u8]`, which is not FFI-safe
 LL | pub fn bad_twice(f: Result<extern "C" fn([u8]), 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
@@ -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<&<T as FooTrait>::FooType>`, which is not FFI-safe
@@ -72,6 +79,7 @@ error: `extern` fn uses type `Option<&<T as FooTrait>::FooType>`, which is not F
 LL | pub type Foo2<T> = extern "C" fn(Option<&<T as FooTrait>::FooType>);
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
    |
+   = note: the function pointer to `for<'a> extern "C" fn(Option<&'a <T as FooTrait>::FooType>)` is FFI-unsafe due to `Option<&<T as FooTrait>::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<extern "C" fn(FfiUnsafe), extern "C" fn(FfiUnsafe)> = 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<extern "C" fn(FfiUnsafe), extern "C" fn(FfiUnsafe)> = 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 b04decd0bca..c4de5a44a96 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 8957758d577..da15b748f21 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 73820c86d1a..e16ff9573fd 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<u32>) { }
 
@@ -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 a62533a4be1..c86c02c8006 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<str>`, which is not FFI-safe
   --> $DIR/lint-ctypes-fn.rs:83:35
@@ -35,7 +36,8 @@ error: `extern` fn uses type `Box<str>`, which is not FFI-safe
 LL | pub extern "C" fn boxed_string(p: Box<str>) { }
    |                                   ^^^^^^^^ 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<dyn Trait>`, which is not FFI-safe
   --> $DIR/lint-ctypes-fn.rs:86:34
@@ -43,7 +45,7 @@ error: `extern` fn uses type `Box<dyn Trait>`, which is not FFI-safe
 LL | pub extern "C" fn boxed_trait(p: Box<dyn Trait>) { }
    |                                  ^^^^^^^^^^^^^^ 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<bool>`, 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 dae07930aba..8c516ab8428 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<T: ?Sized> 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<u32>;
+pub type RustBoxRet = extern "C" fn() -> Box<u32>;
 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<U>(f32, PhantomData<U>);
 #[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<i32>);
@@ -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<u32>); //~ ERROR uses type `Box<u32>`
+    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<u32>);
     pub fn opt_box_type(p: Option<Box<u32>>);
-    //~^ ERROR uses type `Option<Box<u32>>`
     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<bool>; //~ ERROR uses type `PhantomData<bool>`
     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<u32>`
+    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<u32>`
+    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<UnsafeCell<extern fn()>>);
     //~^ ERROR: uses type `Option<UnsafeCell<extern "C" fn()>>`
     pub fn no_niche_b(b: Option<UnsafeCell<&i32>>);
diff --git a/tests/ui/lint/lint-ctypes.stderr b/tests/ui/lint/lint-ctypes.stderr
index 2c81c7b8e4b..8580a10b215 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<u32>`, which is not FFI-safe
-  --> $DIR/lint-ctypes.rs:53:24
-   |
-LL |     pub fn box_type(p: Box<u32>);
-   |                        ^^^^^^^^ 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<Box<u32>>`, which is not FFI-safe
-  --> $DIR/lint-ctypes.rs:54:28
-   |
-LL |     pub fn opt_box_type(p: Option<Box<u32>>);
-   |                            ^^^^^^^^^^^^^^^^ 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<i32>);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `extern` block uses type `PhantomData<bool>`, which is not FFI-safe
-  --> $DIR/lint-ctypes.rs:66:12
+  --> $DIR/lint-ctypes.rs:78:12
    |
 LL |         -> ::std::marker::PhantomData<bool>;
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
@@ -163,7 +148,7 @@ LL |         -> ::std::marker::PhantomData<bool>;
    = 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<u32>`, 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<u32>`, 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<UnsafeCell<extern "C" fn()>>`, 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<UnsafeCell<extern fn()>>);
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
@@ -234,7 +209,7 @@ LL |     pub fn no_niche_a(a: Option<UnsafeCell<extern fn()>>);
    = note: enum has no representation hint
 
 error: `extern` block uses type `Option<UnsafeCell<&i32>>`, 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<UnsafeCell<&i32>>);
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
@@ -243,7 +218,7 @@ LL |     pub fn no_niche_b(b: Option<UnsafeCell<&i32>>);
    = 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