|  | 
|  | 1 | +use rustc_errors::Applicability; | 
|  | 2 | +use rustc_hir::def::{DefKind, Res}; | 
|  | 3 | +use rustc_hir::def_id::LocalDefId; | 
|  | 4 | +use rustc_hir::{self as hir}; | 
|  | 5 | +use rustc_macros::LintDiagnostic; | 
|  | 6 | +use rustc_middle::ty::{self, Ty}; | 
|  | 7 | +use rustc_session::{declare_lint, impl_lint_pass}; | 
|  | 8 | +use rustc_span::sym; | 
|  | 9 | + | 
|  | 10 | +use crate::{LateContext, LateLintPass}; | 
|  | 11 | + | 
|  | 12 | +declare_lint! { | 
|  | 13 | +    /// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer | 
|  | 14 | +    /// transmute in const functions and associated constants. | 
|  | 15 | +    /// | 
|  | 16 | +    /// ### Example | 
|  | 17 | +    /// | 
|  | 18 | +    /// ```rust | 
|  | 19 | +    /// const fn foo(ptr: *const u8) -> usize { | 
|  | 20 | +    ///    unsafe { | 
|  | 21 | +    ///        std::mem::transmute::<*const u8, usize>(ptr) | 
|  | 22 | +    ///    } | 
|  | 23 | +    /// } | 
|  | 24 | +    /// ``` | 
|  | 25 | +    /// | 
|  | 26 | +    /// {{produces}} | 
|  | 27 | +    /// | 
|  | 28 | +    /// ### Explanation | 
|  | 29 | +    /// | 
|  | 30 | +    /// Transmuting pointers to integers in a `const` context is undefined behavior. | 
|  | 31 | +    /// Any attempt to use the resulting integer will abort const-evaluation. | 
|  | 32 | +    /// | 
|  | 33 | +    /// But sometimes the compiler might not emit an error for pointer to integer transmutes | 
|  | 34 | +    /// inside const functions and associated consts because they are evaluated only when referenced. | 
|  | 35 | +    /// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior | 
|  | 36 | +    /// from compiling without any warnings or errors. | 
|  | 37 | +    /// | 
|  | 38 | +    /// See [std::mem::transmute] in the reference for more details. | 
|  | 39 | +    /// | 
|  | 40 | +    /// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html | 
|  | 41 | +    pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, | 
|  | 42 | +    Warn, | 
|  | 43 | +    "detects pointer to integer transmutes in const functions and associated constants", | 
|  | 44 | +} | 
|  | 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 | + | 
|  | 70 | +pub(crate) struct CheckTransmutes; | 
|  | 71 | + | 
|  | 72 | +impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]); | 
|  | 73 | + | 
|  | 74 | +impl<'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 | + | 
|  | 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 | +            ); | 
|  | 130 | +        } | 
|  | 131 | +    } | 
|  | 132 | +} | 
|  | 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 | + | 
|  | 273 | +#[derive(LintDiagnostic)] | 
|  | 274 | +#[diag(lint_undefined_transmute)] | 
|  | 275 | +#[note] | 
|  | 276 | +#[note(lint_note2)] | 
|  | 277 | +#[help] | 
|  | 278 | +pub(crate) struct UndefinedTransmuteLint; | 
0 commit comments