Skip to main content

rustc_mir_transform/
function_item_references.rs

1use itertools::Itertools;
2use rustc_abi::ExternAbi;
3use rustc_hir::def_id::DefId;
4use rustc_middle::mir::visit::Visitor;
5use rustc_middle::mir::*;
6use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt};
7use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES;
8use rustc_span::{Span, Spanned, sym};
9
10use crate::diagnostics;
11
12pub(super) struct FunctionItemReferences;
13
14impl<'tcx> crate::MirLint<'tcx> for FunctionItemReferences {
15    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
16        let mut checker = FunctionItemRefChecker { tcx, body };
17        checker.visit_body(body);
18    }
19}
20
21struct FunctionItemRefChecker<'a, 'tcx> {
22    tcx: TyCtxt<'tcx>,
23    body: &'a Body<'tcx>,
24}
25
26impl<'tcx> Visitor<'tcx> for FunctionItemRefChecker<'_, 'tcx> {
27    /// Emits a lint for function reference arguments bound by `fmt::Pointer` or passed to
28    /// `transmute`. This only handles arguments in calls outside macro expansions to avoid double
29    /// counting function references formatted as pointers by macros.
30    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
31        if let TerminatorKind::Call {
32            func,
33            args,
34            destination: _,
35            target: _,
36            unwind: _,
37            call_source: _,
38            fn_span: _,
39        } = &terminator.kind
40        {
41            let source_info = *self.body.source_info(location);
42            let func_ty = func.ty(self.body, self.tcx);
43            if let ty::FnDef(def_id, args_ref) = *func_ty.kind() {
44                // Handle calls to `transmute`
45                if self.tcx.is_diagnostic_item(sym::transmute, def_id) {
46                    let arg_ty = args[0].node.ty(self.body, self.tcx);
47                    for inner_ty in arg_ty.walk().filter_map(|arg| arg.as_type()) {
48                        if let Some((fn_id, fn_args)) = FunctionItemRefChecker::is_fn_ref(inner_ty)
49                        {
50                            let span = self.nth_arg_span(args, 0);
51                            self.emit_lint(fn_id, fn_args, source_info, span);
52                        }
53                    }
54                } else {
55                    self.check_bound_args(
56                        def_id,
57                        args_ref.no_bound_vars().unwrap(),
58                        args,
59                        source_info,
60                    );
61                }
62            }
63        }
64        self.super_terminator(terminator, location);
65    }
66}
67
68impl<'tcx> FunctionItemRefChecker<'_, 'tcx> {
69    /// Emits a lint for function reference arguments bound by `fmt::Pointer` in calls to the
70    /// function defined by `def_id` with the generic parameters `args_ref`.
71    fn check_bound_args(
72        &self,
73        def_id: DefId,
74        args_ref: GenericArgsRef<'tcx>,
75        args: &[Spanned<Operand<'tcx>>],
76        source_info: SourceInfo,
77    ) {
78        let param_env = self.tcx.param_env(def_id);
79        let bounds = param_env.caller_bounds();
80        for bound in bounds {
81            if let Some(bound_ty) = self.is_pointer_trait(bound) {
82                // Get the argument types as they appear in the function signature.
83                let arg_defs =
84                    self.tcx.fn_sig(def_id).instantiate_identity().skip_binder().inputs();
85                for (arg_num, arg_def) in arg_defs.iter().enumerate() {
86                    // For all types reachable from the argument type in the fn sig
87                    for inner_ty in arg_def.walk().filter_map(|arg| arg.as_type()) {
88                        // If the inner type matches the type bound by `Pointer`
89                        if inner_ty == bound_ty {
90                            // Do an instantiation using the parameters from the callsite
91                            let instantiated_ty = EarlyBinder::bind(self.tcx, inner_ty)
92                                .instantiate(self.tcx, args_ref)
93                                .skip_norm_wip();
94                            if let Some((fn_id, fn_args)) =
95                                FunctionItemRefChecker::is_fn_ref(instantiated_ty)
96                            {
97                                let mut span = self.nth_arg_span(args, arg_num);
98                                if span.from_expansion() {
99                                    // The operand's ctxt wouldn't display the lint since it's
100                                    // inside a macro so we have to use the callsite's ctxt.
101                                    let callsite_ctxt = span.source_callsite().ctxt();
102                                    span = span.with_ctxt(callsite_ctxt);
103                                }
104                                self.emit_lint(fn_id, fn_args, source_info, span);
105                            }
106                        }
107                    }
108                }
109            }
110        }
111    }
112
113    /// If the given predicate is the trait `fmt::Pointer`, returns the bound parameter type.
114    fn is_pointer_trait(&self, bound: ty::Clause<'tcx>) -> Option<Ty<'tcx>> {
115        if let ty::ClauseKind::Trait(predicate) = bound.kind().skip_binder() {
116            self.tcx
117                .is_diagnostic_item(sym::Pointer, predicate.def_id())
118                .then(|| predicate.trait_ref.self_ty())
119        } else {
120            None
121        }
122    }
123
124    /// If a type is a reference or raw pointer to the anonymous type of a function definition,
125    /// returns that function's `DefId` and `GenericArgsRef`.
126    fn is_fn_ref(ty: Ty<'tcx>) -> Option<(DefId, GenericArgsRef<'tcx>)> {
127        let referent_ty = match ty.kind() {
128            ty::Ref(_, referent_ty, _) => Some(referent_ty),
129            ty::RawPtr(referent_ty, _) => Some(referent_ty),
130            _ => None,
131        };
132        referent_ty
133            .map(|ref_ty| {
134                if let ty::FnDef(def_id, args_ref) = *ref_ty.kind() {
135                    Some((def_id, args_ref.no_bound_vars().unwrap()))
136                } else {
137                    None
138                }
139            })
140            .unwrap_or(None)
141    }
142
143    fn nth_arg_span(&self, args: &[Spanned<Operand<'tcx>>], n: usize) -> Span {
144        args[n].node.span(&self.body.local_decls)
145    }
146
147    fn emit_lint(
148        &self,
149        fn_id: DefId,
150        fn_args: GenericArgsRef<'tcx>,
151        source_info: SourceInfo,
152        span: Span,
153    ) {
154        let lint_root = self.body.source_scopes[source_info.scope]
155            .local_data
156            .as_ref()
157            .unwrap_crate_local()
158            .lint_root;
159        // FIXME: use existing printing routines to print the function signature
160        let fn_sig = self.tcx.fn_sig(fn_id).instantiate(self.tcx, fn_args).skip_norm_wip();
161        let unsafety = fn_sig.safety().prefix_str();
162        let abi = match fn_sig.abi() {
163            ExternAbi::Rust => String::from(""),
164            other_abi => format!("extern {other_abi} "),
165        };
166        let ident = self.tcx.item_ident(fn_id);
167        let ty_params = fn_args.types().map(|ty| format!("{ty}"));
168        let const_params = fn_args.consts().map(|c| format!("{c}"));
169        let params = ty_params.chain(const_params).join(", ");
170        let num_args = fn_sig.inputs().map_bound(|inputs| inputs.len()).skip_binder();
171        let variadic = if fn_sig.c_variadic() { ", ..." } else { "" };
172        let ret = if fn_sig.output().skip_binder().is_unit() { "" } else { " -> _" };
173        let sugg = format!(
174            "{} as {}{}fn({}{}){}",
175            if params.is_empty() { ident.to_string() } else { format!("{ident}::<{params}>") },
176            unsafety,
177            abi,
178            vec!["_"; num_args].join(", "),
179            variadic,
180            ret,
181        );
182
183        self.tcx.emit_node_span_lint(
184            FUNCTION_ITEM_REFERENCES,
185            lint_root,
186            span,
187            diagnostics::FnItemRef { span, sugg, ident },
188        );
189    }
190}