1
+ use rustc_errors:: Applicability ;
1
2
use rustc_hir:: def:: { DefKind , Res } ;
3
+ use rustc_hir:: def_id:: LocalDefId ;
2
4
use rustc_hir:: { self as hir} ;
3
5
use rustc_macros:: LintDiagnostic ;
6
+ use rustc_middle:: ty:: { self , Ty } ;
4
7
use rustc_session:: { declare_lint, impl_lint_pass} ;
5
8
use rustc_span:: sym;
6
9
@@ -40,13 +43,37 @@ declare_lint! {
40
43
"detects pointer to integer transmutes in const functions and associated constants" ,
41
44
}
42
45
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
+
43
70
pub ( crate ) struct CheckTransmutes ;
44
71
45
- impl_lint_pass ! ( CheckTransmutes => [ PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS ] ) ;
72
+ impl_lint_pass ! ( CheckTransmutes => [ PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS , UNNECESSARY_TRANSMUTES ] ) ;
46
73
47
74
impl < ' tcx > LateLintPass < ' tcx > for CheckTransmutes {
48
75
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 {
50
77
return ;
51
78
} ;
52
79
let hir:: ExprKind :: Path ( qpath) = callee. kind else {
@@ -59,41 +86,190 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
59
86
return ;
60
87
} ;
61
88
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) ;
65
90
let args = cx. typeck_results ( ) . node_args ( callee. hir_id ) ;
66
91
67
92
let src = args. type_at ( 0 ) ;
68
93
let dst = args. type_at ( 1 ) ;
69
94
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
+ ) ;
93
130
}
94
131
}
95
132
}
96
133
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
+
97
273
#[ derive( LintDiagnostic ) ]
98
274
#[ diag( lint_undefined_transmute) ]
99
275
#[ note]
0 commit comments