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