rustc_lint/types/
improper_ctypes.rs

1use std::iter;
2use std::ops::ControlFlow;
3
4use bitflags::bitflags;
5use rustc_abi::VariantIdx;
6use rustc_data_structures::fx::FxHashSet;
7use rustc_errors::DiagMessage;
8use rustc_hir::def::CtorKind;
9use rustc_hir::intravisit::VisitorExt;
10use rustc_hir::{self as hir, AmbigArg};
11use rustc_middle::bug;
12use rustc_middle::ty::{
13    self, Adt, AdtDef, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
14    TypeVisitableExt,
15};
16use rustc_session::{declare_lint, declare_lint_pass};
17use rustc_span::def_id::LocalDefId;
18use rustc_span::{Span, sym};
19use tracing::debug;
20
21use super::repr_nullable_ptr;
22use crate::lints::{ImproperCTypes, UsesPowerAlignment};
23use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
24
25declare_lint! {
26    /// The `improper_ctypes` lint detects incorrect use of types in foreign
27    /// modules.
28    ///
29    /// ### Example
30    ///
31    /// ```rust
32    /// unsafe extern "C" {
33    ///     static STATIC: String;
34    /// }
35    /// ```
36    ///
37    /// {{produces}}
38    ///
39    /// ### Explanation
40    ///
41    /// The compiler has several checks to verify that types used in `extern`
42    /// blocks are safe and follow certain rules to ensure proper
43    /// compatibility with the foreign interfaces. This lint is issued when it
44    /// detects a probable mistake in a definition. The lint usually should
45    /// provide a description of the issue, along with possibly a hint on how
46    /// to resolve it.
47    IMPROPER_CTYPES,
48    Warn,
49    "proper use of libc types in foreign modules"
50}
51
52declare_lint! {
53    /// The `improper_ctypes_definitions` lint detects incorrect use of
54    /// [`extern` function] definitions.
55    ///
56    /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
57    ///
58    /// ### Example
59    ///
60    /// ```rust
61    /// # #![allow(unused)]
62    /// pub extern "C" fn str_type(p: &str) { }
63    /// ```
64    ///
65    /// {{produces}}
66    ///
67    /// ### Explanation
68    ///
69    /// There are many parameter and return types that may be specified in an
70    /// `extern` function that are not compatible with the given ABI. This
71    /// lint is an alert that these types should not be used. The lint usually
72    /// should provide a description of the issue, along with possibly a hint
73    /// on how to resolve it.
74    IMPROPER_CTYPES_DEFINITIONS,
75    Warn,
76    "proper use of libc types in foreign item definitions"
77}
78
79declare_lint! {
80    /// The `uses_power_alignment` lint detects specific `repr(C)`
81    /// aggregates on AIX.
82    /// In its platform C ABI, AIX uses the "power" (as in PowerPC) alignment
83    /// rule (detailed in https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes#alignment),
84    /// which can also be set for XLC by `#pragma align(power)` or
85    /// `-qalign=power`. Aggregates with a floating-point type as the
86    /// recursively first field (as in "at offset 0") modify the layout of
87    /// *subsequent* fields of the associated structs to use an alignment value
88    /// where the floating-point type is aligned on a 4-byte boundary.
89    ///
90    /// Effectively, subsequent floating-point fields act as-if they are `repr(packed(4))`. This
91    /// would be unsound to do in a `repr(C)` type without all the restrictions that come with
92    /// `repr(packed)`. Rust instead chooses a layout that maintains soundness of Rust code, at the
93    /// expense of incompatibility with C code.
94    ///
95    /// ### Example
96    ///
97    /// ```rust,ignore (fails on non-powerpc64-ibm-aix)
98    /// #[repr(C)]
99    /// pub struct Floats {
100    ///     a: f64,
101    ///     b: u8,
102    ///     c: f64,
103    /// }
104    /// ```
105    ///
106    /// This will produce:
107    ///
108    /// ```text
109    /// warning: repr(C) does not follow the power alignment rule. This may affect platform C ABI compatibility for this type
110    ///  --> <source>:5:3
111    ///   |
112    /// 5 |   c: f64,
113    ///   |   ^^^^^^
114    ///   |
115    ///   = note: `#[warn(uses_power_alignment)]` on by default
116    /// ```
117    ///
118    /// ### Explanation
119    ///
120    /// The power alignment rule specifies that the above struct has the
121    /// following alignment:
122    ///  - offset_of!(Floats, a) == 0
123    ///  - offset_of!(Floats, b) == 8
124    ///  - offset_of!(Floats, c) == 12
125    ///
126    /// However, Rust currently aligns `c` at `offset_of!(Floats, c) == 16`.
127    /// Using offset 12 would be unsound since `f64` generally must be 8-aligned on this target.
128    /// Thus, a warning is produced for the above struct.
129    USES_POWER_ALIGNMENT,
130    Warn,
131    "Structs do not follow the power alignment rule under repr(C)"
132}
133
134declare_lint_pass!(ImproperCTypesLint => [
135    IMPROPER_CTYPES,
136    IMPROPER_CTYPES_DEFINITIONS,
137    USES_POWER_ALIGNMENT
138]);
139
140/// Check a variant of a non-exhaustive enum for improper ctypes
141///
142/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
143/// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
144///
145/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
146/// so we don't need the lint to account for it.
147/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
148pub(crate) fn check_non_exhaustive_variant(
149    non_exhaustive_variant_list: bool,
150    variant: &ty::VariantDef,
151) -> ControlFlow<DiagMessage, ()> {
152    // non_exhaustive suggests it is possible that someone might break ABI
153    // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
154    // so warn on complex enums being used outside their crate
155    if non_exhaustive_variant_list {
156        // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195
157        // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
158        // but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
159        if variant_has_complex_ctor(variant) {
160            return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive);
161        }
162    }
163
164    if variant.field_list_has_applicable_non_exhaustive() {
165        return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant);
166    }
167
168    ControlFlow::Continue(())
169}
170
171fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool {
172    // CtorKind::Const means a "unit" ctor
173    !matches!(variant.ctor_kind(), Some(CtorKind::Const))
174}
175
176/// Per-struct-field function that checks if a struct definition follows
177/// the Power alignment Rule (see the `check_struct_for_power_alignment` function).
178fn check_arg_for_power_alignment<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
179    let tcx = cx.tcx;
180    assert!(tcx.sess.target.os == "aix");
181    // Structs (under repr(C)) follow the power alignment rule if:
182    //   - the first field of the struct is a floating-point type that
183    //     is greater than 4-bytes, or
184    //   - the first field of the struct is an aggregate whose
185    //     recursively first field is a floating-point type greater than
186    //     4 bytes.
187    if ty.is_floating_point() && ty.primitive_size(tcx).bytes() > 4 {
188        return true;
189    } else if let Adt(adt_def, _) = ty.kind()
190        && adt_def.is_struct()
191        && adt_def.repr().c()
192        && !adt_def.repr().packed()
193        && adt_def.repr().align.is_none()
194    {
195        let struct_variant = adt_def.variant(VariantIdx::ZERO);
196        // Within a nested struct, all fields are examined to correctly
197        // report if any fields after the nested struct within the
198        // original struct are misaligned.
199        for struct_field in &struct_variant.fields {
200            let field_ty = tcx.type_of(struct_field.did).instantiate_identity();
201            if check_arg_for_power_alignment(cx, field_ty) {
202                return true;
203            }
204        }
205    }
206    return false;
207}
208
209/// Check a struct definition for respect of the Power alignment Rule (as in PowerPC),
210/// which should be respected in the "aix" target OS.
211/// To do so, we must follow one of the two following conditions:
212/// - The first field of the struct must be floating-point type that
213///    is greater than 4-bytes.
214///  - The first field of the struct must be an aggregate whose
215///    recursively first field is a floating-point type greater than
216///    4 bytes.
217fn check_struct_for_power_alignment<'tcx>(
218    cx: &LateContext<'tcx>,
219    item: &'tcx hir::Item<'tcx>,
220    adt_def: AdtDef<'tcx>,
221) {
222    let tcx = cx.tcx;
223    // repr(C) structs also with packed or aligned representation
224    // should be ignored.
225    if adt_def.repr().c()
226        && !adt_def.repr().packed()
227        && adt_def.repr().align.is_none()
228        && tcx.sess.target.os == "aix"
229        && !adt_def.all_fields().next().is_none()
230    {
231        let struct_variant_data = item.expect_struct().2;
232        for field_def in struct_variant_data.fields().iter().skip(1) {
233            // Struct fields (after the first field) are checked for the
234            // power alignment rule, as fields after the first are likely
235            // to be the fields that are misaligned.
236            let def_id = field_def.def_id;
237            let ty = tcx.type_of(def_id).instantiate_identity();
238            if check_arg_for_power_alignment(cx, ty) {
239                cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment);
240            }
241        }
242    }
243}
244
245#[derive(Clone, Copy)]
246enum CItemKind {
247    Declaration,
248    Definition,
249}
250
251enum FfiResult<'tcx> {
252    FfiSafe,
253    FfiPhantom(Ty<'tcx>),
254    FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
255}
256
257/// The result when a type has been checked but perhaps not completely. `None` indicates that
258/// FFI safety/unsafety has not yet been determined, `Some(res)` indicates that the safety/unsafety
259/// in the `FfiResult` is final.
260type PartialFfiResult<'tcx> = Option<FfiResult<'tcx>>;
261
262bitflags! {
263    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
264    struct VisitorState: u8 {
265        /// For use in (externally-linked) static variables.
266        const STATIC = 0b000001;
267        /// For use in functions in general.
268        const FUNC = 0b000010;
269        /// For variables in function returns (implicitly: not for static variables).
270        const FN_RETURN = 0b000100;
271        /// For variables in functions/variables which are defined in rust.
272        const DEFINED = 0b001000;
273        /// For times where we are only defining the type of something
274        /// (struct/enum/union definitions, FnPtrs).
275        const THEORETICAL = 0b010000;
276    }
277}
278
279impl VisitorState {
280    // The values that can be set.
281    const STATIC_TY: Self = Self::STATIC;
282    const ARGUMENT_TY_IN_DEFINITION: Self =
283        Self::from_bits(Self::FUNC.bits() | Self::DEFINED.bits()).unwrap();
284    const RETURN_TY_IN_DEFINITION: Self =
285        Self::from_bits(Self::FUNC.bits() | Self::FN_RETURN.bits() | Self::DEFINED.bits()).unwrap();
286    const ARGUMENT_TY_IN_DECLARATION: Self = Self::FUNC;
287    const RETURN_TY_IN_DECLARATION: Self =
288        Self::from_bits(Self::FUNC.bits() | Self::FN_RETURN.bits()).unwrap();
289    const ARGUMENT_TY_IN_FNPTR: Self =
290        Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits()).unwrap();
291    const RETURN_TY_IN_FNPTR: Self =
292        Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits() | Self::FN_RETURN.bits())
293            .unwrap();
294
295    /// Get the proper visitor state for a given function's arguments.
296    fn argument_from_fnmode(fn_mode: CItemKind) -> Self {
297        match fn_mode {
298            CItemKind::Definition => VisitorState::ARGUMENT_TY_IN_DEFINITION,
299            CItemKind::Declaration => VisitorState::ARGUMENT_TY_IN_DECLARATION,
300        }
301    }
302
303    /// Get the proper visitor state for a given function's return type.
304    fn return_from_fnmode(fn_mode: CItemKind) -> Self {
305        match fn_mode {
306            CItemKind::Definition => VisitorState::RETURN_TY_IN_DEFINITION,
307            CItemKind::Declaration => VisitorState::RETURN_TY_IN_DECLARATION,
308        }
309    }
310
311    /// Whether the type is used in a function.
312    fn is_in_function(self) -> bool {
313        let ret = self.contains(Self::FUNC);
314        if ret {
315            debug_assert!(!self.contains(Self::STATIC));
316        }
317        ret
318    }
319    /// Whether the type is used (directly or not) in a function, in return position.
320    fn is_in_function_return(self) -> bool {
321        let ret = self.contains(Self::FN_RETURN);
322        if ret {
323            debug_assert!(self.is_in_function());
324        }
325        ret
326    }
327    /// Whether the type is used (directly or not) in a defined function.
328    /// In other words, whether or not we allow non-FFI-safe types behind a C pointer,
329    /// to be treated as an opaque type on the other side of the FFI boundary.
330    fn is_in_defined_function(self) -> bool {
331        self.contains(Self::DEFINED) && self.is_in_function()
332    }
333
334    /// Whether the type is used (directly or not) in a function pointer type.
335    /// Here, we also allow non-FFI-safe types behind a C pointer,
336    /// to be treated as an opaque type on the other side of the FFI boundary.
337    fn is_in_fnptr(self) -> bool {
338        self.contains(Self::THEORETICAL) && self.is_in_function()
339    }
340
341    /// Whether we can expect type parameters and co in a given type.
342    fn can_expect_ty_params(self) -> bool {
343        // rust-defined functions, as well as FnPtrs
344        self.contains(Self::THEORETICAL) || self.is_in_defined_function()
345    }
346}
347
348/// Visitor used to recursively traverse MIR types and evaluate FFI-safety.
349/// It uses ``check_*`` methods as entrypoints to be called elsewhere,
350/// and ``visit_*`` methods to recurse.
351struct ImproperCTypesVisitor<'a, 'tcx> {
352    cx: &'a LateContext<'tcx>,
353    /// To prevent problems with recursive types,
354    /// add a types-in-check cache.
355    cache: FxHashSet<Ty<'tcx>>,
356    /// The original type being checked, before we recursed
357    /// to any other types it contains.
358    base_ty: Ty<'tcx>,
359    base_fn_mode: CItemKind,
360}
361
362impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
363    fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self {
364        Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() }
365    }
366
367    /// Checks if the given field's type is "ffi-safe".
368    fn check_field_type_for_ffi(
369        &mut self,
370        state: VisitorState,
371        field: &ty::FieldDef,
372        args: GenericArgsRef<'tcx>,
373    ) -> FfiResult<'tcx> {
374        let field_ty = field.ty(self.cx.tcx, args);
375        let field_ty = self
376            .cx
377            .tcx
378            .try_normalize_erasing_regions(self.cx.typing_env(), field_ty)
379            .unwrap_or(field_ty);
380        self.visit_type(state, field_ty)
381    }
382
383    /// Checks if the given `VariantDef`'s field types are "ffi-safe".
384    fn check_variant_for_ffi(
385        &mut self,
386        state: VisitorState,
387        ty: Ty<'tcx>,
388        def: ty::AdtDef<'tcx>,
389        variant: &ty::VariantDef,
390        args: GenericArgsRef<'tcx>,
391    ) -> FfiResult<'tcx> {
392        use FfiResult::*;
393        let transparent_with_all_zst_fields = if def.repr().transparent() {
394            if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
395                // Transparent newtypes have at most one non-ZST field which needs to be checked..
396                match self.check_field_type_for_ffi(state, field, args) {
397                    FfiUnsafe { ty, .. } if ty.is_unit() => (),
398                    r => return r,
399                }
400
401                false
402            } else {
403                // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
404                // `PhantomData`).
405                true
406            }
407        } else {
408            false
409        };
410
411        // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
412        let mut all_phantom = !variant.fields.is_empty();
413        for field in &variant.fields {
414            all_phantom &= match self.check_field_type_for_ffi(state, field, args) {
415                FfiSafe => false,
416                // `()` fields are FFI-safe!
417                FfiUnsafe { ty, .. } if ty.is_unit() => false,
418                FfiPhantom(..) => true,
419                r @ FfiUnsafe { .. } => return r,
420            }
421        }
422
423        if all_phantom {
424            FfiPhantom(ty)
425        } else if transparent_with_all_zst_fields {
426            FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
427        } else {
428            FfiSafe
429        }
430    }
431
432    /// Checks if the given type is "ffi-safe" (has a stable, well-defined
433    /// representation which can be exported to C code).
434    fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
435        use FfiResult::*;
436
437        let tcx = self.cx.tcx;
438
439        // Protect against infinite recursion, for example
440        // `struct S(*mut S);`.
441        // FIXME: A recursion limit is necessary as well, for irregular
442        // recursive types.
443        if !self.cache.insert(ty) {
444            return FfiSafe;
445        }
446
447        match *ty.kind() {
448            ty::Adt(def, args) => {
449                if let Some(boxed) = ty.boxed_ty()
450                    && (
451                        // FIXME(ctypes): this logic is broken, but it still fits the current tests
452                        state.is_in_defined_function()
453                            || (state.is_in_fnptr()
454                                && matches!(self.base_fn_mode, CItemKind::Definition))
455                    )
456                {
457                    if boxed.is_sized(tcx, self.cx.typing_env()) {
458                        return FfiSafe;
459                    } else {
460                        return FfiUnsafe {
461                            ty,
462                            reason: fluent::lint_improper_ctypes_box,
463                            help: None,
464                        };
465                    }
466                }
467                if def.is_phantom_data() {
468                    return FfiPhantom(ty);
469                }
470                match def.adt_kind() {
471                    AdtKind::Struct | AdtKind::Union => {
472                        if let Some(sym::cstring_type | sym::cstr_type) =
473                            tcx.get_diagnostic_name(def.did())
474                            && !self.base_ty.is_mutable_ptr()
475                        {
476                            return FfiUnsafe {
477                                ty,
478                                reason: fluent::lint_improper_ctypes_cstr_reason,
479                                help: Some(fluent::lint_improper_ctypes_cstr_help),
480                            };
481                        }
482
483                        if !def.repr().c() && !def.repr().transparent() {
484                            return FfiUnsafe {
485                                ty,
486                                reason: if def.is_struct() {
487                                    fluent::lint_improper_ctypes_struct_layout_reason
488                                } else {
489                                    fluent::lint_improper_ctypes_union_layout_reason
490                                },
491                                help: if def.is_struct() {
492                                    Some(fluent::lint_improper_ctypes_struct_layout_help)
493                                } else {
494                                    Some(fluent::lint_improper_ctypes_union_layout_help)
495                                },
496                            };
497                        }
498
499                        if def.non_enum_variant().field_list_has_applicable_non_exhaustive() {
500                            return FfiUnsafe {
501                                ty,
502                                reason: if def.is_struct() {
503                                    fluent::lint_improper_ctypes_struct_non_exhaustive
504                                } else {
505                                    fluent::lint_improper_ctypes_union_non_exhaustive
506                                },
507                                help: None,
508                            };
509                        }
510
511                        if def.non_enum_variant().fields.is_empty() {
512                            return FfiUnsafe {
513                                ty,
514                                reason: if def.is_struct() {
515                                    fluent::lint_improper_ctypes_struct_fieldless_reason
516                                } else {
517                                    fluent::lint_improper_ctypes_union_fieldless_reason
518                                },
519                                help: if def.is_struct() {
520                                    Some(fluent::lint_improper_ctypes_struct_fieldless_help)
521                                } else {
522                                    Some(fluent::lint_improper_ctypes_union_fieldless_help)
523                                },
524                            };
525                        }
526
527                        self.check_variant_for_ffi(state, ty, def, def.non_enum_variant(), args)
528                    }
529                    AdtKind::Enum => {
530                        if def.variants().is_empty() {
531                            // Empty enums are okay... although sort of useless.
532                            return FfiSafe;
533                        }
534                        // Check for a repr() attribute to specify the size of the
535                        // discriminant.
536                        if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
537                        {
538                            // Special-case types like `Option<extern fn()>` and `Result<extern fn(), ()>`
539                            if let Some(ty) =
540                                repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty)
541                            {
542                                return self.visit_type(state, ty);
543                            }
544
545                            return FfiUnsafe {
546                                ty,
547                                reason: fluent::lint_improper_ctypes_enum_repr_reason,
548                                help: Some(fluent::lint_improper_ctypes_enum_repr_help),
549                            };
550                        }
551
552                        let non_exhaustive = def.variant_list_has_applicable_non_exhaustive();
553                        // Check the contained variants.
554                        let ret = def.variants().iter().try_for_each(|variant| {
555                            check_non_exhaustive_variant(non_exhaustive, variant)
556                                .map_break(|reason| FfiUnsafe { ty, reason, help: None })?;
557
558                            match self.check_variant_for_ffi(state, ty, def, variant, args) {
559                                FfiSafe => ControlFlow::Continue(()),
560                                r => ControlFlow::Break(r),
561                            }
562                        });
563                        if let ControlFlow::Break(result) = ret {
564                            return result;
565                        }
566
567                        FfiSafe
568                    }
569                }
570            }
571
572            ty::Char => FfiUnsafe {
573                ty,
574                reason: fluent::lint_improper_ctypes_char_reason,
575                help: Some(fluent::lint_improper_ctypes_char_help),
576            },
577
578            // It's just extra invariants on the type that you need to uphold,
579            // but only the base type is relevant for being representable in FFI.
580            ty::Pat(base, ..) => self.visit_type(state, base),
581
582            // Primitive types with a stable representation.
583            ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
584
585            ty::Slice(_) => FfiUnsafe {
586                ty,
587                reason: fluent::lint_improper_ctypes_slice_reason,
588                help: Some(fluent::lint_improper_ctypes_slice_help),
589            },
590
591            ty::Dynamic(..) => {
592                FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None }
593            }
594
595            ty::Str => FfiUnsafe {
596                ty,
597                reason: fluent::lint_improper_ctypes_str_reason,
598                help: Some(fluent::lint_improper_ctypes_str_help),
599            },
600
601            ty::Tuple(..) => FfiUnsafe {
602                ty,
603                reason: fluent::lint_improper_ctypes_tuple_reason,
604                help: Some(fluent::lint_improper_ctypes_tuple_help),
605            },
606
607            ty::RawPtr(ty, _) | ty::Ref(_, ty, _)
608                if {
609                    (state.is_in_defined_function() || state.is_in_fnptr())
610                        && ty.is_sized(self.cx.tcx, self.cx.typing_env())
611                } =>
612            {
613                FfiSafe
614            }
615
616            ty::RawPtr(ty, _)
617                if match ty.kind() {
618                    ty::Tuple(tuple) => tuple.is_empty(),
619                    _ => false,
620                } =>
621            {
622                FfiSafe
623            }
624
625            ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.visit_type(state, ty),
626
627            ty::Array(inner_ty, _) => self.visit_type(state, inner_ty),
628
629            ty::FnPtr(sig_tys, hdr) => {
630                let sig = sig_tys.with(hdr);
631                if sig.abi().is_rustic_abi() {
632                    return FfiUnsafe {
633                        ty,
634                        reason: fluent::lint_improper_ctypes_fnptr_reason,
635                        help: Some(fluent::lint_improper_ctypes_fnptr_help),
636                    };
637                }
638
639                let sig = tcx.instantiate_bound_regions_with_erased(sig);
640                for arg in sig.inputs() {
641                    match self.visit_type(VisitorState::ARGUMENT_TY_IN_FNPTR, *arg) {
642                        FfiSafe => {}
643                        r => return r,
644                    }
645                }
646
647                let ret_ty = sig.output();
648                if ret_ty.is_unit() {
649                    return FfiSafe;
650                }
651
652                self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, ret_ty)
653            }
654
655            ty::Foreign(..) => FfiSafe,
656
657            // While opaque types are checked for earlier, if a projection in a struct field
658            // normalizes to an opaque type, then it will reach this branch.
659            ty::Alias(ty::Opaque, ..) => {
660                FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None }
661            }
662
663            // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
664            //  so they are currently ignored for the purposes of this lint.
665            ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..)
666                if state.can_expect_ty_params() =>
667            {
668                FfiSafe
669            }
670
671            ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"),
672
673            ty::Param(..)
674            | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..)
675            | ty::Infer(..)
676            | ty::Bound(..)
677            | ty::Error(_)
678            | ty::Closure(..)
679            | ty::CoroutineClosure(..)
680            | ty::Coroutine(..)
681            | ty::CoroutineWitness(..)
682            | ty::Placeholder(..)
683            | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty),
684        }
685    }
686
687    fn visit_for_opaque_ty(&mut self, ty: Ty<'tcx>) -> PartialFfiResult<'tcx> {
688        struct ProhibitOpaqueTypes;
689        impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for ProhibitOpaqueTypes {
690            type Result = ControlFlow<Ty<'tcx>>;
691
692            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
693                if !ty.has_opaque_types() {
694                    return ControlFlow::Continue(());
695                }
696
697                if let ty::Alias(ty::Opaque, ..) = ty.kind() {
698                    ControlFlow::Break(ty)
699                } else {
700                    ty.super_visit_with(self)
701                }
702            }
703        }
704
705        if let Some(ty) = self
706            .cx
707            .tcx
708            .try_normalize_erasing_regions(self.cx.typing_env(), ty)
709            .unwrap_or(ty)
710            .visit_with(&mut ProhibitOpaqueTypes)
711            .break_value()
712        {
713            Some(FfiResult::FfiUnsafe {
714                ty,
715                reason: fluent::lint_improper_ctypes_opaque,
716                help: None,
717            })
718        } else {
719            None
720        }
721    }
722
723    /// Check if the type is array and emit an unsafe type lint.
724    fn check_for_array_ty(&mut self, ty: Ty<'tcx>) -> PartialFfiResult<'tcx> {
725        if let ty::Array(..) = ty.kind() {
726            Some(FfiResult::FfiUnsafe {
727                ty,
728                reason: fluent::lint_improper_ctypes_array_reason,
729                help: Some(fluent::lint_improper_ctypes_array_help),
730            })
731        } else {
732            None
733        }
734    }
735
736    /// Determine the FFI-safety of a single (MIR) type, given the context of how it is used.
737    fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
738        if let Some(res) = self.visit_for_opaque_ty(ty) {
739            return res;
740        }
741
742        let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty);
743
744        // C doesn't really support passing arrays by value - the only way to pass an array by value
745        // is through a struct. So, first test that the top level isn't an array, and then
746        // recursively check the types inside.
747        if state.is_in_function() {
748            if let Some(res) = self.check_for_array_ty(ty) {
749                return res;
750            }
751        }
752
753        // Don't report FFI errors for unit return types. This check exists here, and not in
754        // the caller (where it would make more sense) so that normalization has definitely
755        // happened.
756        if state.is_in_function_return() && ty.is_unit() {
757            return FfiResult::FfiSafe;
758        }
759
760        self.visit_type(state, ty)
761    }
762}
763
764impl<'tcx> ImproperCTypesLint {
765    /// Find any fn-ptr types with external ABIs in `ty`, and FFI-checks them.
766    /// For example, `Option<extern "C" fn()>` FFI-checks `extern "C" fn()`.
767    fn check_type_for_external_abi_fnptr(
768        &mut self,
769        cx: &LateContext<'tcx>,
770        state: VisitorState,
771        hir_ty: &hir::Ty<'tcx>,
772        ty: Ty<'tcx>,
773        fn_mode: CItemKind,
774    ) {
775        struct FnPtrFinder<'tcx> {
776            spans: Vec<Span>,
777            tys: Vec<Ty<'tcx>>,
778        }
779
780        impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> {
781            fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) {
782                debug!(?ty);
783                if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind
784                    && !abi.is_rustic_abi()
785                {
786                    self.spans.push(ty.span);
787                }
788
789                hir::intravisit::walk_ty(self, ty)
790            }
791        }
792
793        impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> {
794            type Result = ();
795
796            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
797                if let ty::FnPtr(_, hdr) = ty.kind()
798                    && !hdr.abi.is_rustic_abi()
799                {
800                    self.tys.push(ty);
801                }
802
803                ty.super_visit_with(self)
804            }
805        }
806
807        let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() };
808        ty.visit_with(&mut visitor);
809        visitor.visit_ty_unambig(hir_ty);
810
811        let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..));
812        for (fn_ptr_ty, span) in all_types {
813            let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode);
814            // FIXME(ctypes): make a check_for_fnptr
815            let ffi_res = visitor.check_type(state, fn_ptr_ty);
816
817            self.process_ffi_result(cx, span, ffi_res, fn_mode);
818        }
819    }
820
821    /// Regardless of a function's need to be "ffi-safe", look for fn-ptr argument/return types
822    /// that need to be checked for ffi-safety.
823    fn check_fn_for_external_abi_fnptr(
824        &mut self,
825        cx: &LateContext<'tcx>,
826        fn_mode: CItemKind,
827        def_id: LocalDefId,
828        decl: &'tcx hir::FnDecl<'_>,
829    ) {
830        let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
831        let sig = cx.tcx.instantiate_bound_regions_with_erased(sig);
832
833        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
834            let state = VisitorState::argument_from_fnmode(fn_mode);
835            self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode);
836        }
837
838        if let hir::FnRetTy::Return(ret_hir) = decl.output {
839            let state = VisitorState::return_from_fnmode(fn_mode);
840            self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode);
841        }
842    }
843
844    /// For a local definition of a #[repr(C)] struct/enum/union, check that it is indeed FFI-safe.
845    fn check_reprc_adt(
846        &mut self,
847        cx: &LateContext<'tcx>,
848        item: &'tcx hir::Item<'tcx>,
849        adt_def: AdtDef<'tcx>,
850    ) {
851        debug_assert!(
852            adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none()
853        );
854
855        // FIXME(ctypes): this following call is awkward.
856        // is there a way to perform its logic in MIR space rather than HIR space?
857        // (so that its logic can be absorbed into visitor.visit_struct_or_union)
858        check_struct_for_power_alignment(cx, item, adt_def);
859    }
860
861    fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
862        let ty = cx.tcx.type_of(id).instantiate_identity();
863        let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::Declaration);
864        let ffi_res = visitor.check_type(VisitorState::STATIC_TY, ty);
865        self.process_ffi_result(cx, span, ffi_res, CItemKind::Declaration);
866    }
867
868    /// Check if a function's argument types and result type are "ffi-safe".
869    fn check_foreign_fn(
870        &mut self,
871        cx: &LateContext<'tcx>,
872        fn_mode: CItemKind,
873        def_id: LocalDefId,
874        decl: &'tcx hir::FnDecl<'_>,
875    ) {
876        let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
877        let sig = cx.tcx.instantiate_bound_regions_with_erased(sig);
878
879        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
880            let state = VisitorState::argument_from_fnmode(fn_mode);
881            let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode);
882            let ffi_res = visitor.check_type(state, *input_ty);
883            self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
884        }
885
886        if let hir::FnRetTy::Return(ret_hir) = decl.output {
887            let state = VisitorState::return_from_fnmode(fn_mode);
888            let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode);
889            let ffi_res = visitor.check_type(state, sig.output());
890            self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
891        }
892    }
893
894    fn process_ffi_result(
895        &self,
896        cx: &LateContext<'tcx>,
897        sp: Span,
898        res: FfiResult<'tcx>,
899        fn_mode: CItemKind,
900    ) {
901        match res {
902            FfiResult::FfiSafe => {}
903            FfiResult::FfiPhantom(ty) => {
904                self.emit_ffi_unsafe_type_lint(
905                    cx,
906                    ty,
907                    sp,
908                    fluent::lint_improper_ctypes_only_phantomdata,
909                    None,
910                    fn_mode,
911                );
912            }
913            FfiResult::FfiUnsafe { ty, reason, help } => {
914                self.emit_ffi_unsafe_type_lint(cx, ty, sp, reason, help, fn_mode);
915            }
916        }
917    }
918
919    fn emit_ffi_unsafe_type_lint(
920        &self,
921        cx: &LateContext<'tcx>,
922        ty: Ty<'tcx>,
923        sp: Span,
924        note: DiagMessage,
925        help: Option<DiagMessage>,
926        fn_mode: CItemKind,
927    ) {
928        let lint = match fn_mode {
929            CItemKind::Declaration => IMPROPER_CTYPES,
930            CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
931        };
932        let desc = match fn_mode {
933            CItemKind::Declaration => "block",
934            CItemKind::Definition => "fn",
935        };
936        let span_note = if let ty::Adt(def, _) = ty.kind()
937            && let Some(sp) = cx.tcx.hir_span_if_local(def.did())
938        {
939            Some(sp)
940        } else {
941            None
942        };
943        cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, help, note, span_note });
944    }
945}
946
947/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in
948/// `extern "C" { }` blocks):
949///
950/// - `extern "<abi>" fn` definitions are checked in the same way as the
951///   `ImproperCtypesDeclarations` visitor checks functions if `<abi>` is external (e.g. "C").
952/// - All other items which contain types (e.g. other functions, struct definitions, etc) are
953///   checked for extern fn-ptrs with external ABIs.
954impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint {
955    fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) {
956        let abi = cx.tcx.hir_get_foreign_abi(it.hir_id());
957
958        match it.kind {
959            hir::ForeignItemKind::Fn(sig, _, _) => {
960                // fnptrs are a special case, they always need to be treated as
961                // "the element rendered unsafe" because their unsafety doesn't affect
962                // their surroundings, and their type is often declared inline
963                if !abi.is_rustic_abi() {
964                    self.check_foreign_fn(cx, CItemKind::Declaration, it.owner_id.def_id, sig.decl);
965                } else {
966                    self.check_fn_for_external_abi_fnptr(
967                        cx,
968                        CItemKind::Declaration,
969                        it.owner_id.def_id,
970                        sig.decl,
971                    );
972                }
973            }
974            hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => {
975                self.check_foreign_static(cx, it.owner_id, ty.span);
976            }
977            hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
978        }
979    }
980
981    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
982        match item.kind {
983            hir::ItemKind::Static(_, _, ty, _)
984            | hir::ItemKind::Const(_, _, ty, _)
985            | hir::ItemKind::TyAlias(_, _, ty) => {
986                self.check_type_for_external_abi_fnptr(
987                    cx,
988                    VisitorState::STATIC_TY,
989                    ty,
990                    cx.tcx.type_of(item.owner_id).instantiate_identity(),
991                    CItemKind::Definition,
992                );
993            }
994            // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks
995            hir::ItemKind::Fn { .. } => {}
996            hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {
997                // looking for extern FnPtr:s is delegated to `check_field_def`.
998                let adt_def: AdtDef<'tcx> = cx.tcx.adt_def(item.owner_id.to_def_id());
999
1000                if adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none()
1001                {
1002                    self.check_reprc_adt(cx, item, adt_def);
1003                }
1004            }
1005
1006            // Doesn't define something that can contain a external type to be checked.
1007            hir::ItemKind::Impl(..)
1008            | hir::ItemKind::TraitAlias(..)
1009            | hir::ItemKind::Trait(..)
1010            | hir::ItemKind::GlobalAsm { .. }
1011            | hir::ItemKind::ForeignMod { .. }
1012            | hir::ItemKind::Mod(..)
1013            | hir::ItemKind::Macro(..)
1014            | hir::ItemKind::Use(..)
1015            | hir::ItemKind::ExternCrate(..) => {}
1016        }
1017    }
1018
1019    fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) {
1020        self.check_type_for_external_abi_fnptr(
1021            cx,
1022            VisitorState::STATIC_TY,
1023            field.ty,
1024            cx.tcx.type_of(field.def_id).instantiate_identity(),
1025            CItemKind::Definition,
1026        );
1027    }
1028
1029    fn check_fn(
1030        &mut self,
1031        cx: &LateContext<'tcx>,
1032        kind: hir::intravisit::FnKind<'tcx>,
1033        decl: &'tcx hir::FnDecl<'_>,
1034        _: &'tcx hir::Body<'_>,
1035        _: Span,
1036        id: LocalDefId,
1037    ) {
1038        use hir::intravisit::FnKind;
1039
1040        let abi = match kind {
1041            FnKind::ItemFn(_, _, header, ..) => header.abi,
1042            FnKind::Method(_, sig, ..) => sig.header.abi,
1043            _ => return,
1044        };
1045
1046        // fnptrs are a special case, they always need to be treated as
1047        // "the element rendered unsafe" because their unsafety doesn't affect
1048        // their surroundings, and their type is often declared inline
1049        if !abi.is_rustic_abi() {
1050            self.check_foreign_fn(cx, CItemKind::Definition, id, decl);
1051        } else {
1052            self.check_fn_for_external_abi_fnptr(cx, CItemKind::Definition, id, decl);
1053        }
1054    }
1055}