|  | 
|  | 1 | +use rustc_errors::Applicability; | 
|  | 2 | +use rustc_hir::def_id::DefId; | 
|  | 3 | +use rustc_middle::mir::visit::Visitor; | 
|  | 4 | +use rustc_middle::mir::*; | 
|  | 5 | +use rustc_middle::ty::{ | 
|  | 6 | + self, | 
|  | 7 | + subst::{GenericArgKind, Subst, SubstsRef}, | 
|  | 8 | + PredicateAtom, Ty, TyCtxt, TyS, | 
|  | 9 | +}; | 
|  | 10 | +use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES; | 
|  | 11 | +use rustc_span::{symbol::sym, Span}; | 
|  | 12 | +use rustc_target::spec::abi::Abi; | 
|  | 13 | + | 
|  | 14 | +use crate::transform::MirPass; | 
|  | 15 | + | 
|  | 16 | +pub struct FunctionItemReferences; | 
|  | 17 | + | 
|  | 18 | +impl<'tcx> MirPass<'tcx> for FunctionItemReferences { | 
|  | 19 | + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | 
|  | 20 | + let mut checker = FunctionItemRefChecker { tcx, body }; | 
|  | 21 | + checker.visit_body(&body); | 
|  | 22 | + } | 
|  | 23 | +} | 
|  | 24 | + | 
|  | 25 | +struct FunctionItemRefChecker<'a, 'tcx> { | 
|  | 26 | + tcx: TyCtxt<'tcx>, | 
|  | 27 | + body: &'a Body<'tcx>, | 
|  | 28 | +} | 
|  | 29 | + | 
|  | 30 | +impl<'a, 'tcx> Visitor<'tcx> for FunctionItemRefChecker<'a, 'tcx> { | 
|  | 31 | + /// Emits a lint for function reference arguments bound by `fmt::Pointer` or passed to | 
|  | 32 | + /// `transmute`. This only handles arguments in calls outside macro expansions to avoid double | 
|  | 33 | + /// counting function references formatted as pointers by macros. | 
|  | 34 | + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { | 
|  | 35 | + if let TerminatorKind::Call { | 
|  | 36 | + func, | 
|  | 37 | + args, | 
|  | 38 | + destination: _, | 
|  | 39 | + cleanup: _, | 
|  | 40 | + from_hir_call: _, | 
|  | 41 | + fn_span: _, | 
|  | 42 | + } = &terminator.kind | 
|  | 43 | + { | 
|  | 44 | + let source_info = *self.body.source_info(location); | 
|  | 45 | + // Only handle function calls outside macros | 
|  | 46 | + if !source_info.span.from_expansion() { | 
|  | 47 | + let func_ty = func.ty(self.body, self.tcx); | 
|  | 48 | + if let ty::FnDef(def_id, substs_ref) = *func_ty.kind() { | 
|  | 49 | + // Handle calls to `transmute` | 
|  | 50 | + if self.tcx.is_diagnostic_item(sym::transmute, def_id) { | 
|  | 51 | + let arg_ty = args[0].ty(self.body, self.tcx); | 
|  | 52 | + for generic_inner_ty in arg_ty.walk() { | 
|  | 53 | + if let GenericArgKind::Type(inner_ty) = generic_inner_ty.unpack() { | 
|  | 54 | + if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(inner_ty) { | 
|  | 55 | + let ident = self.tcx.item_name(fn_id).to_ident_string(); | 
|  | 56 | + let span = self.nth_arg_span(&args, 0); | 
|  | 57 | + self.emit_lint(ident, fn_id, source_info, span); | 
|  | 58 | + } | 
|  | 59 | + } | 
|  | 60 | + } | 
|  | 61 | + } else { | 
|  | 62 | + self.check_bound_args(def_id, substs_ref, &args, source_info); | 
|  | 63 | + } | 
|  | 64 | + } | 
|  | 65 | + } | 
|  | 66 | + } | 
|  | 67 | + self.super_terminator(terminator, location); | 
|  | 68 | + } | 
|  | 69 | + /// Emits a lint for function references formatted with `fmt::Pointer::fmt` by macros. These | 
|  | 70 | + /// cases are handled as operands instead of call terminators to avoid any dependence on | 
|  | 71 | + /// unstable, internal formatting details like whether `fmt` is called directly or not. | 
|  | 72 | + fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { | 
|  | 73 | + let source_info = *self.body.source_info(location); | 
|  | 74 | + if source_info.span.from_expansion() { | 
|  | 75 | + let op_ty = operand.ty(self.body, self.tcx); | 
|  | 76 | + if let ty::FnDef(def_id, substs_ref) = *op_ty.kind() { | 
|  | 77 | + if self.tcx.is_diagnostic_item(sym::pointer_trait_fmt, def_id) { | 
|  | 78 | + let param_ty = substs_ref.type_at(0); | 
|  | 79 | + if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(param_ty) { | 
|  | 80 | + // The operand's ctxt wouldn't display the lint since it's inside a macro so | 
|  | 81 | + // we have to use the callsite's ctxt. | 
|  | 82 | + let callsite_ctxt = source_info.span.source_callsite().ctxt(); | 
|  | 83 | + let span = source_info.span.with_ctxt(callsite_ctxt); | 
|  | 84 | + let ident = self.tcx.item_name(fn_id).to_ident_string(); | 
|  | 85 | + self.emit_lint(ident, fn_id, source_info, span); | 
|  | 86 | + } | 
|  | 87 | + } | 
|  | 88 | + } | 
|  | 89 | + } | 
|  | 90 | + self.super_operand(operand, location); | 
|  | 91 | + } | 
|  | 92 | +} | 
|  | 93 | + | 
|  | 94 | +impl<'a, 'tcx> FunctionItemRefChecker<'a, 'tcx> { | 
|  | 95 | + /// Emits a lint for function reference arguments bound by `fmt::Pointer` in calls to the | 
|  | 96 | + /// function defined by `def_id` with the substitutions `substs_ref`. | 
|  | 97 | + fn check_bound_args( | 
|  | 98 | + &self, | 
|  | 99 | + def_id: DefId, | 
|  | 100 | + substs_ref: SubstsRef<'tcx>, | 
|  | 101 | + args: &Vec<Operand<'tcx>>, | 
|  | 102 | + source_info: SourceInfo, | 
|  | 103 | + ) { | 
|  | 104 | + let param_env = self.tcx.param_env(def_id); | 
|  | 105 | + let bounds = param_env.caller_bounds(); | 
|  | 106 | + for bound in bounds { | 
|  | 107 | + if let Some(bound_ty) = self.is_pointer_trait(&bound.skip_binders()) { | 
|  | 108 | + // Get the argument types as they appear in the function signature. | 
|  | 109 | + let arg_defs = self.tcx.fn_sig(def_id).skip_binder().inputs(); | 
|  | 110 | + for (arg_num, arg_def) in arg_defs.iter().enumerate() { | 
|  | 111 | + // For all types reachable from the argument type in the fn sig | 
|  | 112 | + for generic_inner_ty in arg_def.walk() { | 
|  | 113 | + if let GenericArgKind::Type(inner_ty) = generic_inner_ty.unpack() { | 
|  | 114 | + // If the inner type matches the type bound by `Pointer` | 
|  | 115 | + if TyS::same_type(inner_ty, bound_ty) { | 
|  | 116 | + // Do a substitution using the parameters from the callsite | 
|  | 117 | + let subst_ty = inner_ty.subst(self.tcx, substs_ref); | 
|  | 118 | + if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(subst_ty) { | 
|  | 119 | + let ident = self.tcx.item_name(fn_id).to_ident_string(); | 
|  | 120 | + let span = self.nth_arg_span(args, arg_num); | 
|  | 121 | + self.emit_lint(ident, fn_id, source_info, span); | 
|  | 122 | + } | 
|  | 123 | + } | 
|  | 124 | + } | 
|  | 125 | + } | 
|  | 126 | + } | 
|  | 127 | + } | 
|  | 128 | + } | 
|  | 129 | + } | 
|  | 130 | + /// If the given predicate is the trait `fmt::Pointer`, returns the bound parameter type. | 
|  | 131 | + fn is_pointer_trait(&self, bound: &PredicateAtom<'tcx>) -> Option<Ty<'tcx>> { | 
|  | 132 | + if let ty::PredicateAtom::Trait(predicate, _) = bound { | 
|  | 133 | + if self.tcx.is_diagnostic_item(sym::pointer_trait, predicate.def_id()) { | 
|  | 134 | + Some(predicate.trait_ref.self_ty()) | 
|  | 135 | + } else { | 
|  | 136 | + None | 
|  | 137 | + } | 
|  | 138 | + } else { | 
|  | 139 | + None | 
|  | 140 | + } | 
|  | 141 | + } | 
|  | 142 | + /// If a type is a reference or raw pointer to the anonymous type of a function definition, | 
|  | 143 | + /// returns that function's `DefId`. | 
|  | 144 | + fn is_fn_ref(ty: Ty<'tcx>) -> Option<DefId> { | 
|  | 145 | + let referent_ty = match ty.kind() { | 
|  | 146 | + ty::Ref(_, referent_ty, _) => Some(referent_ty), | 
|  | 147 | + ty::RawPtr(ty_and_mut) => Some(&ty_and_mut.ty), | 
|  | 148 | + _ => None, | 
|  | 149 | + }; | 
|  | 150 | + referent_ty | 
|  | 151 | + .map( | 
|  | 152 | + |ref_ty| { | 
|  | 153 | + if let ty::FnDef(def_id, _) = *ref_ty.kind() { Some(def_id) } else { None } | 
|  | 154 | + }, | 
|  | 155 | + ) | 
|  | 156 | + .unwrap_or(None) | 
|  | 157 | + } | 
|  | 158 | + fn nth_arg_span(&self, args: &Vec<Operand<'tcx>>, n: usize) -> Span { | 
|  | 159 | + match &args[n] { | 
|  | 160 | + Operand::Copy(place) | Operand::Move(place) => { | 
|  | 161 | + self.body.local_decls[place.local].source_info.span | 
|  | 162 | + } | 
|  | 163 | + Operand::Constant(constant) => constant.span, | 
|  | 164 | + } | 
|  | 165 | + } | 
|  | 166 | + fn emit_lint(&self, ident: String, fn_id: DefId, source_info: SourceInfo, span: Span) { | 
|  | 167 | + let lint_root = self.body.source_scopes[source_info.scope] | 
|  | 168 | + .local_data | 
|  | 169 | + .as_ref() | 
|  | 170 | + .assert_crate_local() | 
|  | 171 | + .lint_root; | 
|  | 172 | + let fn_sig = self.tcx.fn_sig(fn_id); | 
|  | 173 | + let unsafety = fn_sig.unsafety().prefix_str(); | 
|  | 174 | + let abi = match fn_sig.abi() { | 
|  | 175 | + Abi::Rust => String::from(""), | 
|  | 176 | + other_abi => { | 
|  | 177 | + let mut s = String::from("extern \""); | 
|  | 178 | + s.push_str(other_abi.name()); | 
|  | 179 | + s.push_str("\" "); | 
|  | 180 | + s | 
|  | 181 | + } | 
|  | 182 | + }; | 
|  | 183 | + let num_args = fn_sig.inputs().map_bound(|inputs| inputs.len()).skip_binder(); | 
|  | 184 | + let variadic = if fn_sig.c_variadic() { ", ..." } else { "" }; | 
|  | 185 | + let ret = if fn_sig.output().skip_binder().is_unit() { "" } else { " -> _" }; | 
|  | 186 | + self.tcx.struct_span_lint_hir(FUNCTION_ITEM_REFERENCES, lint_root, span, |lint| { | 
|  | 187 | + lint.build("taking a reference to a function item does not give a function pointer") | 
|  | 188 | + .span_suggestion( | 
|  | 189 | + span, | 
|  | 190 | + &format!("cast `{}` to obtain a function pointer", ident), | 
|  | 191 | + format!( | 
|  | 192 | + "{} as {}{}fn({}{}){}", | 
|  | 193 | + ident, | 
|  | 194 | + unsafety, | 
|  | 195 | + abi, | 
|  | 196 | + vec!["_"; num_args].join(", "), | 
|  | 197 | + variadic, | 
|  | 198 | + ret, | 
|  | 199 | + ), | 
|  | 200 | + Applicability::Unspecified, | 
|  | 201 | + ) | 
|  | 202 | + .emit(); | 
|  | 203 | + }); | 
|  | 204 | + } | 
|  | 205 | +} | 
0 commit comments