@@ -148,7 +148,36 @@ declare_clippy_lint! {
148148 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
149149}
150150
151- declare_lint_pass ! ( Ptr => [ PTR_ARG , CMP_NULL , MUT_FROM_REF , INVALID_NULL_PTR_USAGE ] ) ;
151+ declare_clippy_lint ! {
152+ /// ### What it does
153+ /// Use `std::ptr::eq` when applicable
154+ ///
155+ /// ### Why is this bad?
156+ /// `ptr::eq` can be used to compare `&T` references
157+ /// (which coerce to `*const T` implicitly) by their address rather than
158+ /// comparing the values they point to.
159+ ///
160+ /// ### Example
161+ /// ```no_run
162+ /// let a = &[1, 2, 3];
163+ /// let b = &[1, 2, 3];
164+ ///
165+ /// assert!(a as *const _ as usize == b as *const _ as usize);
166+ /// ```
167+ /// Use instead:
168+ /// ```no_run
169+ /// let a = &[1, 2, 3];
170+ /// let b = &[1, 2, 3];
171+ ///
172+ /// assert!(std::ptr::eq(a, b));
173+ /// ```
174+ #[ clippy:: version = "1.49.0" ]
175+ pub PTR_EQ ,
176+ style,
177+ "use `std::ptr::eq` when comparing raw pointers"
178+ }
179+
180+ declare_lint_pass ! ( Ptr => [ PTR_ARG , CMP_NULL , MUT_FROM_REF , INVALID_NULL_PTR_USAGE , PTR_EQ ] ) ;
152181
153182impl < ' tcx > LateLintPass < ' tcx > for Ptr {
154183 fn check_trait_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx TraitItem < ' _ > ) {
@@ -253,10 +282,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
253282 if let ExprKind :: Binary ( op, l, r) = expr. kind
254283 && ( op. node == BinOpKind :: Eq || op. node == BinOpKind :: Ne )
255284 {
256- let non_null_path_snippet = match ( is_null_path ( cx, l) , is_null_path ( cx, r) ) {
257- ( true , false ) if let Some ( sugg) = Sugg :: hir_opt ( cx, r) => sugg. maybe_par ( ) ,
258- ( false , true ) if let Some ( sugg) = Sugg :: hir_opt ( cx, l) => sugg. maybe_par ( ) ,
259- _ => return ,
285+ let non_null_path_snippet = match (
286+ is_lint_allowed ( cx, CMP_NULL , expr. hir_id ) ,
287+ is_null_path ( cx, l) ,
288+ is_null_path ( cx, r) ,
289+ ) {
290+ ( false , true , false ) if let Some ( sugg) = Sugg :: hir_opt ( cx, r) => sugg. maybe_par ( ) ,
291+ ( false , false , true ) if let Some ( sugg) = Sugg :: hir_opt ( cx, l) => sugg. maybe_par ( ) ,
292+ _ => return check_ptr_eq ( cx, expr, op. node , l, r) ,
260293 } ;
261294
262295 span_lint_and_sugg (
@@ -740,3 +773,71 @@ fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
740773 false
741774 }
742775}
776+
777+ fn check_ptr_eq < ' tcx > (
778+ cx : & LateContext < ' tcx > ,
779+ expr : & ' tcx Expr < ' _ > ,
780+ op : BinOpKind ,
781+ left : & ' tcx Expr < ' _ > ,
782+ right : & ' tcx Expr < ' _ > ,
783+ ) {
784+ if expr. span . from_expansion ( ) {
785+ return ;
786+ }
787+
788+ // Remove one level of usize conversion if any
789+ let ( left, right) = match ( expr_as_cast_to_usize ( cx, left) , expr_as_cast_to_usize ( cx, right) ) {
790+ ( Some ( lhs) , Some ( rhs) ) => ( lhs, rhs) ,
791+ _ => ( left, right) ,
792+ } ;
793+
794+ // This lint concerns raw pointers
795+ let ( left_ty, right_ty) = ( cx. typeck_results ( ) . expr_ty ( left) , cx. typeck_results ( ) . expr_ty ( right) ) ;
796+ if !left_ty. is_raw_ptr ( ) || !right_ty. is_raw_ptr ( ) {
797+ return ;
798+ }
799+
800+ let ( left_var, right_var) = ( peel_raw_casts ( cx, left, left_ty) , peel_raw_casts ( cx, right, right_ty) ) ;
801+
802+ if let Some ( left_snip) = left_var. span . get_source_text ( cx)
803+ && let Some ( right_snip) = right_var. span . get_source_text ( cx)
804+ {
805+ let Some ( top_crate) = std_or_core ( cx) else { return } ;
806+ let invert = if op == BinOpKind :: Eq { "" } else { "!" } ;
807+ span_lint_and_sugg (
808+ cx,
809+ PTR_EQ ,
810+ expr. span ,
811+ format ! ( "use `{top_crate}::ptr::eq` when comparing raw pointers" ) ,
812+ "try" ,
813+ format ! ( "{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})" ) ,
814+ Applicability :: MachineApplicable ,
815+ ) ;
816+ }
817+ }
818+
819+ // If the given expression is a cast to a usize, return the lhs of the cast
820+ // E.g., `foo as *const _ as usize` returns `foo as *const _`.
821+ fn expr_as_cast_to_usize < ' tcx > ( cx : & LateContext < ' tcx > , cast_expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
822+ if cx. typeck_results ( ) . expr_ty ( cast_expr) == cx. tcx . types . usize
823+ && let ExprKind :: Cast ( expr, _) = cast_expr. kind
824+ {
825+ Some ( expr)
826+ } else {
827+ None
828+ }
829+ }
830+
831+ // Peel raw casts if the remaining expression can be coerced to it
832+ fn peel_raw_casts < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , expr_ty : Ty < ' tcx > ) -> & ' tcx Expr < ' tcx > {
833+ if let ExprKind :: Cast ( inner, _) = expr. kind
834+ && let ty:: RawPtr ( target_ty, _) = expr_ty. kind ( )
835+ && let inner_ty = cx. typeck_results ( ) . expr_ty ( inner)
836+ && let ty:: RawPtr ( inner_target_ty, _) | ty:: Ref ( _, inner_target_ty, _) = inner_ty. kind ( )
837+ && target_ty == inner_target_ty
838+ {
839+ peel_raw_casts ( cx, inner, inner_ty)
840+ } else {
841+ expr
842+ }
843+ }
0 commit comments