diff --git a/compiler/rustc_mir/src/transform/function_references.rs b/compiler/rustc_mir/src/transform/function_references.rs index 97688c3ea16..6e94fad5d0d 100644 --- a/compiler/rustc_mir/src/transform/function_references.rs +++ b/compiler/rustc_mir/src/transform/function_references.rs @@ -1,7 +1,11 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; -use rustc_middle::ty::{self, subst::GenericArgKind, PredicateAtom, Ty, TyCtxt, TyS}; +use rustc_middle::ty::{ + self, + subst::{GenericArgKind, Subst}, + PredicateAtom, Ty, TyCtxt, TyS, +}; use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES; use rustc_span::{symbol::sym, Span}; use rustc_target::spec::abi::Abi; @@ -33,48 +37,50 @@ impl<'a, 'tcx> Visitor<'tcx> for FunctionItemRefChecker<'a, 'tcx> { fn_span: _, } = &terminator.kind { - let func_ty = func.ty(self.body, self.tcx); - if let ty::FnDef(def_id, substs_ref) = *func_ty.kind() { - //check arguments for `std::mem::transmute` - if self.tcx.is_diagnostic_item(sym::transmute, def_id) { - let arg_ty = args[0].ty(self.body, self.tcx); - for generic_inner_ty in arg_ty.walk() { - if let GenericArgKind::Type(inner_ty) = generic_inner_ty.unpack() { - if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(inner_ty) { - let ident = self.tcx.item_name(fn_id).to_ident_string(); - let source_info = *self.body.source_info(location); - let span = self.nth_arg_span(&args, 0); - self.emit_lint(ident, fn_id, source_info, span); + let source_info = *self.body.source_info(location); + //this handles all function calls outside macros + if !source_info.span.from_expansion() { + let func_ty = func.ty(self.body, self.tcx); + if let ty::FnDef(def_id, substs_ref) = *func_ty.kind() { + //handle `std::mem::transmute` + if self.tcx.is_diagnostic_item(sym::transmute, def_id) { + let arg_ty = args[0].ty(self.body, self.tcx); + for generic_inner_ty in arg_ty.walk() { + if let GenericArgKind::Type(inner_ty) = generic_inner_ty.unpack() { + if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(inner_ty) { + let ident = self.tcx.item_name(fn_id).to_ident_string(); + let span = self.nth_arg_span(&args, 0); + self.emit_lint(ident, fn_id, source_info, span); + } } } - } - } else { - //check arguments for any function with `std::fmt::Pointer` as a bound trait - let param_env = self.tcx.param_env(def_id); - let bounds = param_env.caller_bounds(); - for bound in bounds { - if let Some(bound_ty) = self.is_pointer_trait(&bound.skip_binders()) { - let arg_defs = self.tcx.fn_sig(def_id).skip_binder().inputs(); - for (arg_num, arg_def) in arg_defs.iter().enumerate() { - for generic_inner_ty in arg_def.walk() { - if let GenericArgKind::Type(inner_ty) = - generic_inner_ty.unpack() - { - //if any type reachable from the argument types in the fn sig matches the type bound by `Pointer` - if TyS::same_type(inner_ty, bound_ty) { - //check if this type is a function reference in the function call - let norm_ty = - self.tcx.subst_and_normalize_erasing_regions( - substs_ref, param_env, &inner_ty, - ); - if let Some(fn_id) = - FunctionItemRefChecker::is_fn_ref(norm_ty) - { - let ident = - self.tcx.item_name(fn_id).to_ident_string(); - let source_info = *self.body.source_info(location); - let span = self.nth_arg_span(&args, arg_num); - self.emit_lint(ident, fn_id, source_info, span); + } else { + //handle any function call with `std::fmt::Pointer` as a bound trait + //this includes calls to `std::fmt::Pointer::fmt` outside of macros + let param_env = self.tcx.param_env(def_id); + let bounds = param_env.caller_bounds(); + for bound in bounds { + if let Some(bound_ty) = self.is_pointer_trait(&bound.skip_binders()) { + //get the argument types as they appear in the function signature + let arg_defs = self.tcx.fn_sig(def_id).skip_binder().inputs(); + for (arg_num, arg_def) in arg_defs.iter().enumerate() { + //for all types reachable from the argument type in the fn sig + for generic_inner_ty in arg_def.walk() { + if let GenericArgKind::Type(inner_ty) = + generic_inner_ty.unpack() + { + //if the inner type matches the type bound by `Pointer` + if TyS::same_type(inner_ty, bound_ty) { + //do a substitution using the parameters from the callsite + let subst_ty = inner_ty.subst(self.tcx, substs_ref); + if let Some(fn_id) = + FunctionItemRefChecker::is_fn_ref(subst_ty) + { + let ident = + self.tcx.item_name(fn_id).to_ident_string(); + let span = self.nth_arg_span(&args, arg_num); + self.emit_lint(ident, fn_id, source_info, span); + } } } } @@ -87,19 +93,25 @@ impl<'a, 'tcx> Visitor<'tcx> for FunctionItemRefChecker<'a, 'tcx> { } self.super_terminator(terminator, location); } - //check for `std::fmt::Pointer::::fmt` where T is a function reference - //this is used in formatting macros, but doesn't rely on the specific expansion + //This handles `std::fmt::Pointer::fmt` when it's used in the formatting macros. + //It's handled as an operand instead of a Call terminator so it won't depend on + //whether the formatting macros call `fmt` directly, transmute it first or other + //internal fmt details. fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { - let op_ty = operand.ty(self.body, self.tcx); - if let ty::FnDef(def_id, substs_ref) = *op_ty.kind() { - if self.tcx.is_diagnostic_item(sym::pointer_trait_fmt, def_id) { - let param_ty = substs_ref.type_at(0); - if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(param_ty) { - let source_info = *self.body.source_info(location); - let callsite_ctxt = source_info.span.source_callsite().ctxt(); - let span = source_info.span.with_ctxt(callsite_ctxt); - let ident = self.tcx.item_name(fn_id).to_ident_string(); - self.emit_lint(ident, fn_id, source_info, span); + let source_info = *self.body.source_info(location); + if source_info.span.from_expansion() { + let op_ty = operand.ty(self.body, self.tcx); + if let ty::FnDef(def_id, substs_ref) = *op_ty.kind() { + if self.tcx.is_diagnostic_item(sym::pointer_trait_fmt, def_id) { + let param_ty = substs_ref.type_at(0); + if let Some(fn_id) = FunctionItemRefChecker::is_fn_ref(param_ty) { + //the operand's ctxt wouldn't display the lint since it's inside a macro + //so we have to use the callsite's ctxt + let callsite_ctxt = source_info.span.source_callsite().ctxt(); + let span = source_info.span.with_ctxt(callsite_ctxt); + let ident = self.tcx.item_name(fn_id).to_ident_string(); + self.emit_lint(ident, fn_id, source_info, span); + } } } } diff --git a/src/test/ui/lint/function-references.rs b/src/test/ui/lint/function-references.rs index 9ec3871e482..28c66a31322 100644 --- a/src/test/ui/lint/function-references.rs +++ b/src/test/ui/lint/function-references.rs @@ -2,6 +2,7 @@ #![feature(c_variadic)] #![warn(function_item_references)] use std::fmt::Pointer; +use std::fmt::Formatter; fn nop() { } fn foo() -> u32 { 42 } @@ -20,6 +21,25 @@ fn parameterized_call_fn u32>(f: &F, x: u32) { f(x); } fn print_ptr(f: F) { println!("{:p}", f); } fn bound_by_ptr_trait(_f: F) { } fn bound_by_ptr_trait_tuple(_t: (F, G)) { } +fn implicit_ptr_trait(f: &F) { println!("{:p}", f); } + +//case found in tinyvec that triggered a compiler error in an earlier version of the lint checker +trait HasItem { + type Item; + fn assoc_item(&self) -> Self::Item; +} +fn _format_assoc_item(data: T, f: &mut Formatter) -> std::fmt::Result + where T::Item: Pointer { + //when the arg type bound by `Pointer` is an associated type, we shouldn't attempt to normalize + Pointer::fmt(&data.assoc_item(), f) +} + +//simple test to make sure that calls to `Pointer::fmt` aren't double counted +fn _call_pointer_fmt(f: &mut Formatter) -> std::fmt::Result { + let zst_ref = &foo; + Pointer::fmt(&zst_ref, f) + //~^ WARNING cast `foo` with `as fn() -> _` to obtain a function pointer +} fn main() { //`let` bindings with function references shouldn't lint @@ -126,6 +146,7 @@ fn main() { bound_by_ptr_trait_tuple((&foo, &bar)); //~^ WARNING cast `foo` with `as fn() -> _` to obtain a function pointer //~^^ WARNING cast `bar` with `as fn(_) -> _` to obtain a function pointer + implicit_ptr_trait(&bar); // ignore //correct ways to pass function pointers as arguments bound by std::fmt::Pointer print_ptr(bar as fn(u32) -> u32); diff --git a/src/test/ui/lint/function-references.stderr b/src/test/ui/lint/function-references.stderr index 71940a1d4cf..5e399c16305 100644 --- a/src/test/ui/lint/function-references.stderr +++ b/src/test/ui/lint/function-references.stderr @@ -1,8 +1,8 @@ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:57:22 + --> $DIR/function-references.rs:40:18 | -LL | println!("{:p}", &foo); - | ^^^^ +LL | Pointer::fmt(&zst_ref, f) + | ^^^^^^^^ | note: the lint level is defined here --> $DIR/function-references.rs:3:9 @@ -11,160 +11,166 @@ LL | #![warn(function_item_references)] | ^^^^^^^^^^^^^^^^^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:59:20 + --> $DIR/function-references.rs:77:22 + | +LL | println!("{:p}", &foo); + | ^^^^ + +warning: cast `foo` with `as fn() -> _` to obtain a function pointer + --> $DIR/function-references.rs:79:20 | LL | print!("{:p}", &foo); | ^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:61:21 + --> $DIR/function-references.rs:81:21 | LL | format!("{:p}", &foo); | ^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:64:22 + --> $DIR/function-references.rs:84:22 | LL | println!("{:p}", &foo as *const _); | ^^^^^^^^^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:66:22 + --> $DIR/function-references.rs:86:22 | LL | println!("{:p}", zst_ref); | ^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:68:22 + --> $DIR/function-references.rs:88:22 | LL | println!("{:p}", cast_zst_ptr); | ^^^^^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:70:22 + --> $DIR/function-references.rs:90:22 | LL | println!("{:p}", coerced_zst_ptr); | ^^^^^^^^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:73:22 + --> $DIR/function-references.rs:93:22 | LL | println!("{:p}", &fn_item); | ^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:75:22 + --> $DIR/function-references.rs:95:22 | LL | println!("{:p}", indirect_ref); | ^^^^^^^^^^^^ warning: cast `nop` with `as fn()` to obtain a function pointer - --> $DIR/function-references.rs:78:22 + --> $DIR/function-references.rs:98:22 | LL | println!("{:p}", &nop); | ^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:80:22 + --> $DIR/function-references.rs:100:22 | LL | println!("{:p}", &bar); | ^^^^ warning: cast `baz` with `as fn(_, _) -> _` to obtain a function pointer - --> $DIR/function-references.rs:82:22 + --> $DIR/function-references.rs:102:22 | LL | println!("{:p}", &baz); | ^^^^ warning: cast `unsafe_fn` with `as unsafe fn()` to obtain a function pointer - --> $DIR/function-references.rs:84:22 + --> $DIR/function-references.rs:104:22 | LL | println!("{:p}", &unsafe_fn); | ^^^^^^^^^^ warning: cast `c_fn` with `as extern "C" fn()` to obtain a function pointer - --> $DIR/function-references.rs:86:22 + --> $DIR/function-references.rs:106:22 | LL | println!("{:p}", &c_fn); | ^^^^^ warning: cast `unsafe_c_fn` with `as unsafe extern "C" fn()` to obtain a function pointer - --> $DIR/function-references.rs:88:22 + --> $DIR/function-references.rs:108:22 | LL | println!("{:p}", &unsafe_c_fn); | ^^^^^^^^^^^^ warning: cast `variadic` with `as unsafe extern "C" fn(_, ...)` to obtain a function pointer - --> $DIR/function-references.rs:90:22 + --> $DIR/function-references.rs:110:22 | LL | println!("{:p}", &variadic); | ^^^^^^^^^ warning: cast `var` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:92:22 + --> $DIR/function-references.rs:112:22 | LL | println!("{:p}", &std::env::var::); | ^^^^^^^^^^^^^^^^^^^^^^^^ warning: cast `nop` with `as fn()` to obtain a function pointer - --> $DIR/function-references.rs:95:32 + --> $DIR/function-references.rs:115:32 | LL | println!("{:p} {:p} {:p}", &nop, &foo, &bar); | ^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:95:38 + --> $DIR/function-references.rs:115:38 | LL | println!("{:p} {:p} {:p}", &nop, &foo, &bar); | ^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:95:44 + --> $DIR/function-references.rs:115:44 | LL | println!("{:p} {:p} {:p}", &nop, &foo, &bar); | ^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:110:41 + --> $DIR/function-references.rs:130:41 | LL | std::mem::transmute::<_, usize>(&foo); | ^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:112:50 + --> $DIR/function-references.rs:132:50 | LL | std::mem::transmute::<_, (usize, usize)>((&foo, &bar)); | ^^^^^^^^^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:112:50 + --> $DIR/function-references.rs:132:50 | LL | std::mem::transmute::<_, (usize, usize)>((&foo, &bar)); | ^^^^^^^^^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:122:15 + --> $DIR/function-references.rs:142:15 | LL | print_ptr(&bar); | ^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:124:24 + --> $DIR/function-references.rs:144:24 | LL | bound_by_ptr_trait(&bar); | ^^^^ warning: cast `bar` with `as fn(_) -> _` to obtain a function pointer - --> $DIR/function-references.rs:126:30 + --> $DIR/function-references.rs:146:30 | LL | bound_by_ptr_trait_tuple((&foo, &bar)); | ^^^^^^^^^^^^ warning: cast `foo` with `as fn() -> _` to obtain a function pointer - --> $DIR/function-references.rs:126:30 + --> $DIR/function-references.rs:146:30 | LL | bound_by_ptr_trait_tuple((&foo, &bar)); | ^^^^^^^^^^^^ -warning: 27 warnings emitted +warning: 28 warnings emitted