@@ -7,8 +7,10 @@ use rustc_ast::ast;
77use rustc_errors:: Applicability ;
88use rustc_hir:: { Expr , ExprKind , GenericArg , Mutability , QPath , TyKind , UnOp } ;
99use rustc_lint:: { LateContext , LateLintPass } ;
10- use rustc_middle:: ty:: { self , Ty } ;
10+ use rustc_middle:: ty:: { self , cast :: CastKind , Ty } ;
1111use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
12+ use rustc_span:: DUMMY_SP ;
13+ use rustc_typeck:: check:: { cast:: CastCheck , FnCtxt , Inherited } ;
1214use std:: borrow:: Cow ;
1315
1416declare_clippy_lint ! {
@@ -48,6 +50,31 @@ declare_clippy_lint! {
4850 "transmutes that have the same to and from types or could be a cast/coercion"
4951}
5052
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
65+ /// # let p: *const [i32] = &[];
66+ /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) };
67+ /// ```
68+ /// Use instead:
69+ /// ```rust
70+ /// # let p: *const [i32] = &[];
71+ /// p as *const [u16];
72+ /// ```
73+ pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
74+ complexity,
75+ "transmutes that could be a pointer cast"
76+ }
77+
5178declare_clippy_lint ! {
5279 /// **What it does:** Checks for transmutes between a type `T` and `*T`.
5380 ///
@@ -269,6 +296,7 @@ declare_clippy_lint! {
269296 correctness,
270297 "transmute between collections of layout-incompatible types"
271298}
299+
272300declare_lint_pass ! ( Transmute => [
273301 CROSSPOINTER_TRANSMUTE ,
274302 TRANSMUTE_PTR_TO_REF ,
@@ -281,6 +309,7 @@ declare_lint_pass!(Transmute => [
281309 TRANSMUTE_INT_TO_FLOAT ,
282310 TRANSMUTE_FLOAT_TO_INT ,
283311 UNSOUND_COLLECTION_TRANSMUTE ,
312+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
284313] ) ;
285314
286315// used to check for UNSOUND_COLLECTION_TRANSMUTE
@@ -601,7 +630,25 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
601630 ) ;
602631 }
603632 } ,
604- _ => return ,
633+ ( _, _) if can_be_expressed_as_pointer_cast( cx, e, from_ty, to_ty) => span_lint_and_then(
634+ cx,
635+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS ,
636+ e. span,
637+ & format!(
638+ "transmute from `{}` to `{}` which could be expressed as a pointer cast instead" ,
639+ from_ty,
640+ to_ty
641+ ) ,
642+ |diag| {
643+ if let Some ( arg) = sugg:: Sugg :: hir_opt( cx, & args[ 0 ] ) {
644+ let sugg = arg. as_ty( & to_ty. to_string( ) ) . to_string( ) ;
645+ diag. span_suggestion( e. span, "try" , sugg, Applicability :: MachineApplicable ) ;
646+ }
647+ }
648+ ) ,
649+ _ => {
650+ return ;
651+ } ,
605652 }
606653 }
607654 }
@@ -648,3 +695,59 @@ fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'
648695 false
649696 }
650697}
698+
699+ /// Check if the type conversion can be expressed as a pointer cast, instead of
700+ /// a transmute. In certain cases, including some invalid casts from array
701+ /// references to pointers, this may cause additional errors to be emitted and/or
702+ /// ICE error messages. This function will panic if that occurs.
703+ fn can_be_expressed_as_pointer_cast < ' tcx > (
704+ cx : & LateContext < ' tcx > ,
705+ e : & ' tcx Expr < ' _ > ,
706+ from_ty : Ty < ' tcx > ,
707+ to_ty : Ty < ' tcx > ,
708+ ) -> bool {
709+ use CastKind :: { AddrPtrCast , ArrayPtrCast , FnPtrAddrCast , FnPtrPtrCast , PtrAddrCast , PtrPtrCast } ;
710+ matches ! (
711+ check_cast( cx, e, from_ty, to_ty) ,
712+ Some ( PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast )
713+ )
714+ }
715+
716+ /// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of
717+ /// the cast. In certain cases, including some invalid casts from array references
718+ /// to pointers, this may cause additional errors to be emitted and/or ICE error
719+ /// messages. This function will panic if that occurs.
720+ fn check_cast < ' tcx > ( cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > , from_ty : Ty < ' tcx > , to_ty : Ty < ' tcx > ) -> Option < CastKind > {
721+ let hir_id = e. hir_id ;
722+ let local_def_id = hir_id. owner ;
723+
724+ Inherited :: build ( cx. tcx , local_def_id) . enter ( |inherited| {
725+ let fn_ctxt = FnCtxt :: new ( & inherited, cx. param_env , hir_id) ;
726+
727+ // If we already have errors, we can't be sure we can pointer cast.
728+ assert ! (
729+ !fn_ctxt. errors_reported_since_creation( ) ,
730+ "Newly created FnCtxt contained errors"
731+ ) ;
732+
733+ if let Ok ( check) = CastCheck :: new (
734+ & fn_ctxt, e, from_ty, to_ty,
735+ // We won't show any error to the user, so we don't care what the span is here.
736+ DUMMY_SP , DUMMY_SP ,
737+ ) {
738+ let res = check. do_check ( & fn_ctxt) ;
739+
740+ // do_check's documentation says that it might return Ok and create
741+ // errors in the fcx instead of returing Err in some cases. Those cases
742+ // should be filtered out before getting here.
743+ assert ! (
744+ !fn_ctxt. errors_reported_since_creation( ) ,
745+ "`fn_ctxt` contained errors after cast check!"
746+ ) ;
747+
748+ res. ok ( )
749+ } else {
750+ None
751+ }
752+ } )
753+ }
0 commit comments