@@ -7,8 +7,10 @@ use rustc_ast::ast;
7
7
use rustc_errors:: Applicability ;
8
8
use rustc_hir:: { Expr , ExprKind , GenericArg , Mutability , QPath , TyKind , UnOp } ;
9
9
use rustc_lint:: { LateContext , LateLintPass } ;
10
- use rustc_middle:: ty:: { self , Ty } ;
10
+ use rustc_middle:: ty:: { self , cast :: CastKind , Ty } ;
11
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
12
+ use rustc_span:: DUMMY_SP ;
13
+ use rustc_typeck:: check:: { cast:: CastCheck , FnCtxt , Inherited } ;
12
14
use std:: borrow:: Cow ;
13
15
14
16
declare_clippy_lint ! {
@@ -48,6 +50,29 @@ declare_clippy_lint! {
48
50
"transmutes that have the same to and from types or could be a cast/coercion"
49
51
}
50
52
53
+ // FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery.
54
+ declare_clippy_lint ! {
55
+ /// **What it does:**Checks for transmutes that could be a pointer cast.
56
+ ///
57
+ /// **Why is this bad?** Readability. The code tricks people into thinking that
58
+ /// something complex is going on.
59
+ ///
60
+ /// **Known problems:** None.
61
+ ///
62
+ /// **Example:**
63
+ ///
64
+ /// ```rust,ignore
65
+ /// core::intrinsics::transmute::<*const [i32], *const [u16]>(p)
66
+ /// ```
67
+ /// Use instead:
68
+ /// ```rust
69
+ /// p as *const [u16]
70
+ /// ```
71
+ pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
72
+ complexity,
73
+ "transmutes that could be a pointer cast"
74
+ }
75
+
51
76
declare_clippy_lint ! {
52
77
/// **What it does:** Checks for transmutes between a type `T` and `*T`.
53
78
///
@@ -269,6 +294,7 @@ declare_clippy_lint! {
269
294
correctness,
270
295
"transmute between collections of layout-incompatible types"
271
296
}
297
+
272
298
declare_lint_pass ! ( Transmute => [
273
299
CROSSPOINTER_TRANSMUTE ,
274
300
TRANSMUTE_PTR_TO_REF ,
@@ -281,6 +307,7 @@ declare_lint_pass!(Transmute => [
281
307
TRANSMUTE_INT_TO_FLOAT ,
282
308
TRANSMUTE_FLOAT_TO_INT ,
283
309
UNSOUND_COLLECTION_TRANSMUTE ,
310
+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
284
311
] ) ;
285
312
286
313
// used to check for UNSOUND_COLLECTION_TRANSMUTE
@@ -601,7 +628,25 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
601
628
) ;
602
629
}
603
630
} ,
604
- _ => return ,
631
+ ( _, _) if can_be_expressed_as_pointer_cast( cx, e, from_ty, to_ty) => span_lint_and_then(
632
+ cx,
633
+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
634
+ e. span,
635
+ & format!(
636
+ "transmute from `{}` to `{}` which could be expressed as a pointer cast instead" ,
637
+ from_ty,
638
+ to_ty
639
+ ) ,
640
+ |diag| {
641
+ if let Some ( arg) = sugg:: Sugg :: hir_opt( cx, & args[ 0 ] ) {
642
+ let sugg = arg. as_ty( & to_ty. to_string( ) ) . to_string( ) ;
643
+ diag. span_suggestion( e. span, "try" , sugg, Applicability :: MachineApplicable ) ;
644
+ }
645
+ }
646
+ ) ,
647
+ _ => {
648
+ return ;
649
+ } ,
605
650
}
606
651
}
607
652
}
@@ -648,3 +693,59 @@ fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'
648
693
false
649
694
}
650
695
}
696
+
697
+ /// Check if the type conversion can be expressed as a pointer cast, instead of
698
+ /// a transmute. In certain cases, including some invalid casts from array
699
+ /// references to pointers, this may cause additional errors to be emitted and/or
700
+ /// ICE error messages. This function will panic if that occurs.
701
+ fn can_be_expressed_as_pointer_cast < ' tcx > (
702
+ cx : & LateContext < ' tcx > ,
703
+ e : & ' tcx Expr < ' _ > ,
704
+ from_ty : Ty < ' tcx > ,
705
+ to_ty : Ty < ' tcx > ,
706
+ ) -> bool {
707
+ use CastKind :: * ;
708
+ matches ! (
709
+ check_cast( cx, e, from_ty, to_ty) ,
710
+ Some ( PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast )
711
+ )
712
+ }
713
+
714
+ /// If a cast from from_ty to to_ty is valid, returns an Ok containing the kind of
715
+ /// the cast. In certain cases, including some invalid casts from array references
716
+ /// to pointers, this may cause additional errors to be emitted and/or ICE error
717
+ /// messages. This function will panic if that occurs.
718
+ fn check_cast < ' tcx > ( cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > , from_ty : Ty < ' tcx > , to_ty : Ty < ' tcx > ) -> Option < CastKind > {
719
+ let hir_id = e. hir_id ;
720
+ let local_def_id = hir_id. owner ;
721
+
722
+ Inherited :: build ( cx. tcx , local_def_id) . enter ( |inherited| {
723
+ let fn_ctxt = FnCtxt :: new ( & inherited, cx. param_env , hir_id) ;
724
+
725
+ // If we already have errors, we can't be sure we can pointer cast.
726
+ assert ! (
727
+ !fn_ctxt. errors_reported_since_creation( ) ,
728
+ "Newly created FnCtxt contained errors"
729
+ ) ;
730
+
731
+ if let Ok ( check) = CastCheck :: new (
732
+ & fn_ctxt, e, from_ty, to_ty,
733
+ // We won't show any error to the user, so we don't care what the span is here.
734
+ DUMMY_SP , DUMMY_SP ,
735
+ ) {
736
+ let res = check. do_check ( & fn_ctxt) ;
737
+
738
+ // do_check's documentation says that it might return Ok and create
739
+ // errors in the fcx instead of returing Err in some cases. Those cases
740
+ // should be filtered out before getting here.
741
+ assert ! (
742
+ !fn_ctxt. errors_reported_since_creation( ) ,
743
+ "`fn_ctxt` contained errors after cast check!"
744
+ ) ;
745
+
746
+ res. ok ( )
747
+ } else {
748
+ None
749
+ }
750
+ } )
751
+ }
0 commit comments