11mod crosspointer_transmute;
2+ mod eager_transmute;
23mod transmute_float_to_int;
34mod transmute_int_to_bool;
45mod transmute_int_to_char;
@@ -463,6 +464,62 @@ declare_clippy_lint! {
463464 "transmute results in a null function pointer, which is undefined behavior"
464465}
465466
467+ declare_clippy_lint ! {
468+ /// ### What it does
469+ /// Checks for integer validity checks, followed by a transmute that is (incorrectly) evaluated
470+ /// eagerly (e.g. using `bool::then_some`).
471+ ///
472+ /// ### Why is this bad?
473+ /// Eager evaluation means that the `transmute` call is executed regardless of whether the condition is true or false.
474+ /// This can introduce unsoundness and other subtle bugs.
475+ ///
476+ /// ### Example
477+ /// Consider the following function which is meant to convert an unsigned integer to its enum equivalent via transmute.
478+ ///
479+ /// ```no_run
480+ /// #[repr(u8)]
481+ /// enum Opcode {
482+ /// Add = 0,
483+ /// Sub = 1,
484+ /// Mul = 2,
485+ /// Div = 3
486+ /// }
487+ ///
488+ /// fn int_to_opcode(op: u8) -> Option<Opcode> {
489+ /// (op < 4).then_some(unsafe { std::mem::transmute(op) })
490+ /// }
491+ /// ```
492+ /// This may appear fine at first given that it checks that the `u8` is within the validity range of the enum,
493+ /// *however* the transmute is evaluated eagerly, meaning that it executes even if `op >= 4`!
494+ ///
495+ /// This makes the function unsound, because it is possible for the caller to cause undefined behavior
496+ /// (creating an enum with an invalid bitpattern) entirely in safe code only by passing an incorrect value,
497+ /// which is normally only a bug that is possible in unsafe code.
498+ ///
499+ /// One possible way in which this can go wrong practically is that the compiler sees it as:
500+ /// ```rust,ignore (illustrative)
501+ /// let temp: Foo = unsafe { std::mem::transmute(op) };
502+ /// (0 < 4).then_some(temp)
503+ /// ```
504+ /// and optimizes away the `(0 < 4)` check based on the assumption that since a `Foo` was created from `op` with the validity range `0..3`,
505+ /// it is **impossible** for this condition to be false.
506+ ///
507+ /// In short, it is possible for this function to be optimized in a way that makes it [never return `None`](https://godbolt.org/z/ocrcenevq),
508+ /// even if passed the value `4`.
509+ ///
510+ /// This can be avoided by instead using lazy evaluation. For the example above, this should be written:
511+ /// ```rust,ignore (illustrative)
512+ /// fn int_to_opcode(op: u8) -> Option<Opcode> {
513+ /// (op < 4).then(|| unsafe { std::mem::transmute(op) })
514+ /// ^^^^ ^^ `bool::then` only executes the closure if the condition is true!
515+ /// }
516+ /// ```
517+ #[ clippy:: version = "1.76.0" ]
518+ pub EAGER_TRANSMUTE ,
519+ correctness,
520+ "eager evaluation of `transmute`"
521+ }
522+
466523pub struct Transmute {
467524 msrv : Msrv ,
468525}
@@ -484,6 +541,7 @@ impl_lint_pass!(Transmute => [
484541 TRANSMUTE_UNDEFINED_REPR ,
485542 TRANSMUTING_NULL ,
486543 TRANSMUTE_NULL_TO_FN ,
544+ EAGER_TRANSMUTE ,
487545] ) ;
488546impl Transmute {
489547 #[ must_use]
@@ -530,7 +588,8 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
530588 | transmute_float_to_int:: check ( cx, e, from_ty, to_ty, arg, const_context)
531589 | transmute_num_to_bytes:: check ( cx, e, from_ty, to_ty, arg, const_context)
532590 | ( unsound_collection_transmute:: check ( cx, e, from_ty, to_ty)
533- || transmute_undefined_repr:: check ( cx, e, from_ty, to_ty) ) ;
591+ || transmute_undefined_repr:: check ( cx, e, from_ty, to_ty) )
592+ | ( eager_transmute:: check ( cx, e, arg, from_ty, to_ty) ) ;
534593
535594 if !linted {
536595 transmutes_expressible_as_ptr_casts:: check ( cx, e, from_ty, from_ty_adjusted, to_ty, arg) ;
0 commit comments