1use rustc_errors::Applicability;
2use rustc_hir::def::{DefKind, Res};
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::{self as hir};
5use rustc_macros::LintDiagnostic;
6use rustc_middle::ty::{self, Ty};
7use rustc_session::{declare_lint, impl_lint_pass};
8use rustc_span::sym;
9
10use crate::{LateContext, LateLintPass};
11
12declare_lint! {
13 pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
42 Warn,
43 "detects pointer to integer transmutes in const functions and associated constants",
44}
45
46declare_lint! {
47 pub UNNECESSARY_TRANSMUTES,
66 Warn,
67 "detects transmutes that can also be achieved by other operations"
68}
69
70pub(crate) struct CheckTransmutes;
71
72impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
73
74impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
75 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
76 let hir::ExprKind::Call(callee, [arg]) = expr.kind else {
77 return;
78 };
79 let hir::ExprKind::Path(qpath) = callee.kind else {
80 return;
81 };
82 let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else {
83 return;
84 };
85 if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
86 return;
87 };
88 let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
89 let const_context = cx.tcx.hir_body_const_context(body_owner_def_id);
90 let args = cx.typeck_results().node_args(callee.hir_id);
91
92 let src = args.type_at(0);
93 let dst = args.type_at(1);
94
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
100fn 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 );
130 }
131 }
132}
133
134fn 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 (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 (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 (ty::Char, ty::Uint(ty::UintTy::U32)) => {
174 (Some(vec![(callee_span, "u32::from".to_string())]), None)
175 }
176 (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 (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 (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 (ty::Uint(_), ty::Int(_)) => {
199 (Some(vec![(callee_span, format!("{src}::cast_signed"))]), None)
200 }
201 (ty::Int(_), ty::Uint(_)) => {
203 (Some(vec![(callee_span, format!("{src}::cast_unsigned"))]), None)
204 }
205 (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 (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 (ty::Float(_), ty::Uint(..)) => {
223 (Some(vec![(callee_span, format!("{src}::to_bits"))]), None)
224 }
225 (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 (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 (ty::Uint(_), ty::Float(_)) => {
243 (Some(vec![(callee_span, format!("{dst}::from_bits"))]), None)
244 }
245 (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 (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
273#[derive(LintDiagnostic)]
274#[diag(lint_undefined_transmute)]
275#[note]
276#[note(lint_note2)]
277#[help]
278pub(crate) struct UndefinedTransmuteLint;