@@ -23,7 +23,9 @@ use crate::lints::{
2323 AmbiguousWidePointerComparisons , AmbiguousWidePointerComparisonsAddrMetadataSuggestion ,
2424 AmbiguousWidePointerComparisonsAddrSuggestion , AtomicOrderingFence , AtomicOrderingLoad ,
2525 AtomicOrderingStore , ImproperCTypes , InvalidAtomicOrderingDiag , InvalidNanComparisons ,
26- InvalidNanComparisonsSuggestion , UnusedComparisons , VariantSizeDifferencesDiag ,
26+ InvalidNanComparisonsSuggestion , UnpredictableFunctionPointerComparisons ,
27+ UnpredictableFunctionPointerComparisonsSuggestion , UnusedComparisons ,
28+ VariantSizeDifferencesDiag ,
2729} ;
2830use crate :: { LateContext , LateLintPass , LintContext , fluent_generated as fluent} ;
2931
@@ -166,6 +168,35 @@ declare_lint! {
166168 "detects ambiguous wide pointer comparisons"
167169}
168170
171+ declare_lint ! {
172+ /// The `unpredictable_function_pointer_comparisons` lint checks comparison
173+ /// of function pointer as the operands.
174+ ///
175+ /// ### Example
176+ ///
177+ /// ```rust
178+ /// fn a() {}
179+ /// fn b() {}
180+ ///
181+ /// let f: fn() = a;
182+ /// let g: fn() = b;
183+ ///
184+ /// let _ = f == g;
185+ /// ```
186+ ///
187+ /// {{produces}}
188+ ///
189+ /// ### Explanation
190+ ///
191+ /// Function pointers comparisons do not produce meaningful result since
192+ /// they are never guaranteed to be unique and could vary between different
193+ /// code generation units. Furthermore, different functions could have the
194+ /// same address after being merged together.
195+ UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS ,
196+ Warn ,
197+ "detects unpredictable function pointer comparisons"
198+ }
199+
169200#[ derive( Copy , Clone , Default ) ]
170201pub ( crate ) struct TypeLimits {
171202 /// Id of the last visited negated expression
@@ -178,7 +209,8 @@ impl_lint_pass!(TypeLimits => [
178209 UNUSED_COMPARISONS ,
179210 OVERFLOWING_LITERALS ,
180211 INVALID_NAN_COMPARISONS ,
181- AMBIGUOUS_WIDE_POINTER_COMPARISONS
212+ AMBIGUOUS_WIDE_POINTER_COMPARISONS ,
213+ UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS
182214] ) ;
183215
184216impl TypeLimits {
@@ -255,7 +287,7 @@ fn lint_nan<'tcx>(
255287 cx. emit_span_lint ( INVALID_NAN_COMPARISONS , e. span , lint) ;
256288}
257289
258- #[ derive( Debug , PartialEq ) ]
290+ #[ derive( Debug , PartialEq , Copy , Clone ) ]
259291enum ComparisonOp {
260292 BinOp ( hir:: BinOpKind ) ,
261293 Other ,
@@ -383,6 +415,100 @@ fn lint_wide_pointer<'tcx>(
383415 ) ;
384416}
385417
418+ fn lint_fn_pointer < ' tcx > (
419+ cx : & LateContext < ' tcx > ,
420+ e : & ' tcx hir:: Expr < ' tcx > ,
421+ cmpop : ComparisonOp ,
422+ l : & ' tcx hir:: Expr < ' tcx > ,
423+ r : & ' tcx hir:: Expr < ' tcx > ,
424+ ) {
425+ let peel_refs = |mut ty : Ty < ' tcx > | -> ( Ty < ' tcx > , usize ) {
426+ let mut refs = 0 ;
427+
428+ while let ty:: Ref ( _, inner_ty, _) = ty. kind ( ) {
429+ ty = * inner_ty;
430+ refs += 1 ;
431+ }
432+
433+ ( ty, refs)
434+ } ;
435+
436+ // Left and right operands can have borrows, remove them
437+ let l = l. peel_borrows ( ) ;
438+ let r = r. peel_borrows ( ) ;
439+
440+ let Some ( l_ty) = cx. typeck_results ( ) . expr_ty_opt ( l) else { return } ;
441+ let Some ( r_ty) = cx. typeck_results ( ) . expr_ty_opt ( r) else { return } ;
442+
443+ // Remove any references as `==` will deref through them (and count the
444+ // number of references removed, for latter).
445+ let ( l_ty, l_ty_refs) = peel_refs ( l_ty) ;
446+ let ( r_ty, r_ty_refs) = peel_refs ( r_ty) ;
447+
448+ if !l_ty. is_fn ( ) || !r_ty. is_fn ( ) {
449+ return ;
450+ }
451+
452+ // Let's try to suggest `ptr::fn_addr_eq` if/when possible.
453+
454+ let is_eq_ne = matches ! ( cmpop, ComparisonOp :: BinOp ( hir:: BinOpKind :: Eq | hir:: BinOpKind :: Ne ) ) ;
455+
456+ if !is_eq_ne {
457+ // Neither `==` nor `!=`, we can't suggest `ptr::fn_addr_eq`, just show the warning.
458+ return cx. emit_span_lint (
459+ UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS ,
460+ e. span ,
461+ UnpredictableFunctionPointerComparisons :: Warn ,
462+ ) ;
463+ }
464+
465+ let ( Some ( l_span) , Some ( r_span) ) =
466+ ( l. span . find_ancestor_inside ( e. span ) , r. span . find_ancestor_inside ( e. span ) )
467+ else {
468+ // No appropriate spans for the left and right operands, just show the warning.
469+ return cx. emit_span_lint (
470+ UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS ,
471+ e. span ,
472+ UnpredictableFunctionPointerComparisons :: Warn ,
473+ ) ;
474+ } ;
475+
476+ let ne = if cmpop == ComparisonOp :: BinOp ( hir:: BinOpKind :: Ne ) { "!" } else { "" } ;
477+
478+ // `ptr::fn_addr_eq` only works with raw pointer, deref any references.
479+ let deref_left = & * "*" . repeat ( l_ty_refs) ;
480+ let deref_right = & * "*" . repeat ( r_ty_refs) ;
481+
482+ let left = e. span . shrink_to_lo ( ) . until ( l_span. shrink_to_lo ( ) ) ;
483+ let middle = l_span. shrink_to_hi ( ) . until ( r_span. shrink_to_lo ( ) ) ;
484+ let right = r_span. shrink_to_hi ( ) . until ( e. span . shrink_to_hi ( ) ) ;
485+
486+ // We only check for a right cast as `FnDef` == `FnPtr` is not possible,
487+ // only `FnPtr == FnDef` is possible.
488+ let cast_right = if !r_ty. is_fn_ptr ( ) {
489+ let fn_sig = r_ty. fn_sig ( cx. tcx ) ;
490+ format ! ( " as {fn_sig}" )
491+ } else {
492+ String :: new ( )
493+ } ;
494+
495+ cx. emit_span_lint (
496+ UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS ,
497+ e. span ,
498+ UnpredictableFunctionPointerComparisons :: Suggestion {
499+ sugg : UnpredictableFunctionPointerComparisonsSuggestion {
500+ ne,
501+ deref_left,
502+ deref_right,
503+ left,
504+ middle,
505+ right,
506+ cast_right,
507+ } ,
508+ } ,
509+ ) ;
510+ }
511+
386512impl < ' tcx > LateLintPass < ' tcx > for TypeLimits {
387513 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx hir:: Expr < ' tcx > ) {
388514 match e. kind {
@@ -399,7 +525,9 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
399525 cx. emit_span_lint ( UNUSED_COMPARISONS , e. span , UnusedComparisons ) ;
400526 } else {
401527 lint_nan ( cx, e, binop, l, r) ;
402- lint_wide_pointer ( cx, e, ComparisonOp :: BinOp ( binop. node ) , l, r) ;
528+ let cmpop = ComparisonOp :: BinOp ( binop. node ) ;
529+ lint_wide_pointer ( cx, e, cmpop, l, r) ;
530+ lint_fn_pointer ( cx, e, cmpop, l, r) ;
403531 }
404532 }
405533 }
@@ -411,13 +539,15 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
411539 && let Some ( cmpop) = diag_item_cmpop ( diag_item) =>
412540 {
413541 lint_wide_pointer ( cx, e, cmpop, l, r) ;
542+ lint_fn_pointer ( cx, e, cmpop, l, r) ;
414543 }
415544 hir:: ExprKind :: MethodCall ( _, l, [ r] , _)
416545 if let Some ( def_id) = cx. typeck_results ( ) . type_dependent_def_id ( e. hir_id )
417546 && let Some ( diag_item) = cx. tcx . get_diagnostic_name ( def_id)
418547 && let Some ( cmpop) = diag_item_cmpop ( diag_item) =>
419548 {
420549 lint_wide_pointer ( cx, e, cmpop, l, r) ;
550+ lint_fn_pointer ( cx, e, cmpop, l, r) ;
421551 }
422552 _ => { }
423553 } ;
0 commit comments