1
1
mod crosspointer_transmute;
2
+ mod eager_transmute;
2
3
mod transmute_float_to_int;
3
4
mod transmute_int_to_bool;
4
5
mod transmute_int_to_char;
@@ -463,6 +464,62 @@ declare_clippy_lint! {
463
464
"transmute results in a null function pointer, which is undefined behavior"
464
465
}
465
466
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
+
466
523
pub struct Transmute {
467
524
msrv : Msrv ,
468
525
}
@@ -484,6 +541,7 @@ impl_lint_pass!(Transmute => [
484
541
TRANSMUTE_UNDEFINED_REPR ,
485
542
TRANSMUTING_NULL ,
486
543
TRANSMUTE_NULL_TO_FN ,
544
+ EAGER_TRANSMUTE ,
487
545
] ) ;
488
546
impl Transmute {
489
547
#[ must_use]
@@ -530,7 +588,8 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
530
588
| transmute_float_to_int:: check ( cx, e, from_ty, to_ty, arg, const_context)
531
589
| transmute_num_to_bytes:: check ( cx, e, from_ty, to_ty, arg, const_context)
532
590
| ( 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) ) ;
534
593
535
594
if !linted {
536
595
transmutes_expressible_as_ptr_casts:: check ( cx, e, from_ty, from_ty_adjusted, to_ty, arg) ;
0 commit comments