1+ use rustc_errors:: Applicability ;
12use rustc_hir:: def:: { DefKind , Res } ;
3+ use rustc_hir:: def_id:: LocalDefId ;
24use rustc_hir:: { self as hir} ;
35use rustc_macros:: LintDiagnostic ;
6+ use rustc_middle:: ty:: { self , Ty } ;
47use rustc_session:: { declare_lint, impl_lint_pass} ;
58use rustc_span:: sym;
69
@@ -40,13 +43,37 @@ declare_lint! {
4043 "detects pointer to integer transmutes in const functions and associated constants" ,
4144}
4245
46+ declare_lint ! {
47+ /// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
48+ ///
49+ /// ### Example
50+ ///
51+ /// ```rust
52+ /// fn bytes_at_home(x: [u8; 4]) -> u32 {
53+ /// unsafe { std::mem::transmute(x) }
54+ /// }
55+ /// ```
56+ ///
57+ /// {{produces}}
58+ ///
59+ /// ### Explanation
60+ ///
61+ /// Using an explicit method is preferable over calls to
62+ /// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
63+ /// they more clearly communicate the intent, are easier to review, and
64+ /// are less likely to accidentally result in unsoundness.
65+ pub UNNECESSARY_TRANSMUTES ,
66+ Warn ,
67+ "detects transmutes that can also be achieved by other operations"
68+ }
69+
4370pub ( crate ) struct CheckTransmutes ;
4471
45- impl_lint_pass ! ( CheckTransmutes => [ PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS ] ) ;
72+ impl_lint_pass ! ( CheckTransmutes => [ PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS , UNNECESSARY_TRANSMUTES ] ) ;
4673
4774impl < ' tcx > LateLintPass < ' tcx > for CheckTransmutes {
4875 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' tcx > ) {
49- let hir:: ExprKind :: Call ( callee, _ ) = expr. kind else {
76+ let hir:: ExprKind :: Call ( callee, [ arg ] ) = expr. kind else {
5077 return ;
5178 } ;
5279 let hir:: ExprKind :: Path ( qpath) = callee. kind else {
@@ -59,41 +86,190 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
5986 return ;
6087 } ;
6188 let body_owner_def_id = cx. tcx . hir_enclosing_body_owner ( expr. hir_id ) ;
62- let Some ( context) = cx. tcx . hir_body_const_context ( body_owner_def_id) else {
63- return ;
64- } ;
89+ let const_context = cx. tcx . hir_body_const_context ( body_owner_def_id) ;
6590 let args = cx. typeck_results ( ) . node_args ( callee. hir_id ) ;
6691
6792 let src = args. type_at ( 0 ) ;
6893 let dst = args. type_at ( 1 ) ;
6994
70- // Check for transmutes that exhibit undefined behavior.
71- // For example, transmuting pointers to integers in a const context.
72- //
73- // Why do we consider const functions and associated constants only?
74- //
75- // Generally, undefined behavior in const items are handled by the evaluator.
76- // But, const functions and associated constants are evaluated only when referenced.
77- // This can result in undefined behavior in a library going unnoticed until
78- // the function or constant is actually used.
79- //
80- // Therefore, we only consider const functions and associated constants here and leave
81- // other const items to be handled by the evaluator.
82- if matches ! ( context, hir:: ConstContext :: ConstFn )
83- || matches ! ( cx. tcx. def_kind( body_owner_def_id) , DefKind :: AssocConst )
84- {
85- if src. is_raw_ptr ( ) && dst. is_integral ( ) {
86- cx. tcx . emit_node_span_lint (
87- PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS ,
88- expr. hir_id ,
89- expr. span ,
90- UndefinedTransmuteLint ,
91- ) ;
92- }
95+ check_ptr_transmute_in_const ( cx, expr, body_owner_def_id, const_context, src, dst) ;
96+ check_unnecessary_transmute ( cx, expr, callee, arg, const_context, src, dst) ;
97+ }
98+ }
99+
100+ /// Check for transmutes that exhibit undefined behavior.
101+ /// For example, transmuting pointers to integers in a const context.
102+ ///
103+ /// Why do we consider const functions and associated constants only?
104+ ///
105+ /// Generally, undefined behavior in const items are handled by the evaluator.
106+ /// But, const functions and associated constants are evaluated only when referenced.
107+ /// This can result in undefined behavior in a library going unnoticed until
108+ /// the function or constant is actually used.
109+ ///
110+ /// Therefore, we only consider const functions and associated constants here and leave
111+ /// other const items to be handled by the evaluator.
112+ fn check_ptr_transmute_in_const < ' tcx > (
113+ cx : & LateContext < ' tcx > ,
114+ expr : & ' tcx hir:: Expr < ' tcx > ,
115+ body_owner_def_id : LocalDefId ,
116+ const_context : Option < hir:: ConstContext > ,
117+ src : Ty < ' tcx > ,
118+ dst : Ty < ' tcx > ,
119+ ) {
120+ if matches ! ( const_context, Some ( hir:: ConstContext :: ConstFn ) )
121+ || matches ! ( cx. tcx. def_kind( body_owner_def_id) , DefKind :: AssocConst )
122+ {
123+ if src. is_raw_ptr ( ) && dst. is_integral ( ) {
124+ cx. tcx . emit_node_span_lint (
125+ PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS ,
126+ expr. hir_id ,
127+ expr. span ,
128+ UndefinedTransmuteLint ,
129+ ) ;
93130 }
94131 }
95132}
96133
134+ /// Check for transmutes that overlap with stdlib methods.
135+ /// For example, transmuting `[u8; 4]` to `u32`.
136+ ///
137+ /// We chose not to lint u8 -> bool transmutes, see #140431.
138+ fn check_unnecessary_transmute < ' tcx > (
139+ cx : & LateContext < ' tcx > ,
140+ expr : & ' tcx hir:: Expr < ' tcx > ,
141+ callee : & ' tcx hir:: Expr < ' tcx > ,
142+ arg : & ' tcx hir:: Expr < ' tcx > ,
143+ const_context : Option < hir:: ConstContext > ,
144+ src : Ty < ' tcx > ,
145+ dst : Ty < ' tcx > ,
146+ ) {
147+ let callee_span = callee. span . find_ancestor_inside ( expr. span ) . unwrap_or ( callee. span ) ;
148+ let ( sugg, help) = match ( src. kind ( ) , dst. kind ( ) ) {
149+ // dont check the length; transmute does that for us.
150+ // [u8; _] => primitive
151+ ( ty:: Array ( t, _) , ty:: Uint ( _) | ty:: Float ( _) | ty:: Int ( _) )
152+ if * t. kind ( ) == ty:: Uint ( ty:: UintTy :: U8 ) =>
153+ {
154+ (
155+ Some ( vec ! [ ( callee_span, format!( "{dst}::from_ne_bytes" ) ) ] ) ,
156+ Some (
157+ "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order" ,
158+ ) ,
159+ )
160+ }
161+ // primitive => [u8; _]
162+ ( ty:: Uint ( _) | ty:: Float ( _) | ty:: Int ( _) , ty:: Array ( t, _) )
163+ if * t. kind ( ) == ty:: Uint ( ty:: UintTy :: U8 ) =>
164+ {
165+ (
166+ Some ( vec ! [ ( callee_span, format!( "{src}::to_ne_bytes" ) ) ] ) ,
167+ Some (
168+ "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order" ,
169+ ) ,
170+ )
171+ }
172+ // char → u32
173+ ( ty:: Char , ty:: Uint ( ty:: UintTy :: U32 ) ) => {
174+ ( Some ( vec ! [ ( callee_span, "u32::from" . to_string( ) ) ] ) , None )
175+ }
176+ // char (→ u32) → i32
177+ ( ty:: Char , ty:: Int ( ty:: IntTy :: I32 ) ) => (
178+ Some ( vec ! [
179+ ( callee_span, "u32::from" . to_string( ) ) ,
180+ ( expr. span. shrink_to_hi( ) , ".cast_signed()" . to_string( ) ) ,
181+ ] ) ,
182+ None ,
183+ ) ,
184+ // u32 → char
185+ ( ty:: Uint ( ty:: UintTy :: U32 ) , ty:: Char ) => (
186+ Some ( vec ! [ ( callee_span, "char::from_u32_unchecked" . to_string( ) ) ] ) ,
187+ Some ( "consider using `char::from_u32(…).unwrap()`" ) ,
188+ ) ,
189+ // i32 → char
190+ ( ty:: Int ( ty:: IntTy :: I32 ) , ty:: Char ) => (
191+ Some ( vec ! [
192+ ( callee_span, "char::from_u32_unchecked(i32::cast_unsigned" . to_string( ) ) ,
193+ ( expr. span. shrink_to_hi( ) , ")" . to_string( ) ) ,
194+ ] ) ,
195+ Some ( "consider using `char::from_u32(i32::cast_unsigned(…)).unwrap()`" ) ,
196+ ) ,
197+ // uNN → iNN
198+ ( ty:: Uint ( _) , ty:: Int ( _) ) => {
199+ ( Some ( vec ! [ ( callee_span, format!( "{src}::cast_signed" ) ) ] ) , None )
200+ }
201+ // iNN → uNN
202+ ( ty:: Int ( _) , ty:: Uint ( _) ) => {
203+ ( Some ( vec ! [ ( callee_span, format!( "{src}::cast_unsigned" ) ) ] ) , None )
204+ }
205+ // fNN → usize, isize
206+ ( ty:: Float ( _) , ty:: Uint ( ty:: UintTy :: Usize ) | ty:: Int ( ty:: IntTy :: Isize ) ) => (
207+ Some ( vec ! [
208+ ( callee_span, format!( "{src}::to_bits" ) ) ,
209+ ( expr. span. shrink_to_hi( ) , format!( " as {dst}" ) ) ,
210+ ] ) ,
211+ None ,
212+ ) ,
213+ // fNN (→ uNN) → iNN
214+ ( ty:: Float ( _) , ty:: Int ( ..) ) => (
215+ Some ( vec ! [
216+ ( callee_span, format!( "{src}::to_bits" ) ) ,
217+ ( expr. span. shrink_to_hi( ) , ".cast_signed()" . to_string( ) ) ,
218+ ] ) ,
219+ None ,
220+ ) ,
221+ // fNN → uNN
222+ ( ty:: Float ( _) , ty:: Uint ( ..) ) => {
223+ ( Some ( vec ! [ ( callee_span, format!( "{src}::to_bits" ) ) ] ) , None )
224+ }
225+ // xsize → fNN
226+ ( ty:: Uint ( ty:: UintTy :: Usize ) | ty:: Int ( ty:: IntTy :: Isize ) , ty:: Float ( _) ) => (
227+ Some ( vec ! [
228+ ( callee_span, format!( "{dst}::from_bits" ) ) ,
229+ ( arg. span. shrink_to_hi( ) , " as _" . to_string( ) ) ,
230+ ] ) ,
231+ None ,
232+ ) ,
233+ // iNN (→ uNN) → fNN
234+ ( ty:: Int ( _) , ty:: Float ( _) ) => (
235+ Some ( vec ! [
236+ ( callee_span, format!( "{dst}::from_bits({src}::cast_unsigned" ) ) ,
237+ ( expr. span. shrink_to_hi( ) , ")" . to_string( ) ) ,
238+ ] ) ,
239+ None ,
240+ ) ,
241+ // uNN → fNN
242+ ( ty:: Uint ( _) , ty:: Float ( _) ) => {
243+ ( Some ( vec ! [ ( callee_span, format!( "{dst}::from_bits" ) ) ] ) , None )
244+ }
245+ // bool → x8 in const context since `From::from` is not const yet
246+ // FIXME: Consider arg expr's precedence to avoid parentheses.
247+ // FIXME(const_traits): Remove this when `From::from` is constified.
248+ ( ty:: Bool , ty:: Int ( ..) | ty:: Uint ( ..) ) if const_context. is_some ( ) => (
249+ Some ( vec ! [
250+ ( callee_span, "" . to_string( ) ) ,
251+ ( expr. span. shrink_to_hi( ) , format!( " as {dst}" ) ) ,
252+ ] ) ,
253+ None ,
254+ ) ,
255+ // bool → x8 using `x8::from`
256+ ( ty:: Bool , ty:: Int ( ..) | ty:: Uint ( ..) ) => {
257+ ( Some ( vec ! [ ( callee_span, format!( "{dst}::from" ) ) ] ) , None )
258+ }
259+ _ => return ,
260+ } ;
261+
262+ cx. tcx . node_span_lint ( UNNECESSARY_TRANSMUTES , expr. hir_id , expr. span , |diag| {
263+ diag. primary_message ( "unnecessary transmute" ) ;
264+ if let Some ( sugg) = sugg {
265+ diag. multipart_suggestion ( "replace this with" , sugg, Applicability :: MachineApplicable ) ;
266+ }
267+ if let Some ( help) = help {
268+ diag. help ( help) ;
269+ }
270+ } ) ;
271+ }
272+
97273#[ derive( LintDiagnostic ) ]
98274#[ diag( lint_undefined_transmute) ]
99275#[ note]
0 commit comments