diff --git a/clippy_lints/src/transmute.rs b/clippy_lints/src/transmute.rs index fde6abe48af..d01a63f0494 100644 --- a/clippy_lints/src/transmute.rs +++ b/clippy_lints/src/transmute.rs @@ -80,15 +80,24 @@ declare_lint! { /// **What it does:** Checks for transmutes from an integer to a `char`. /// -/// **Why is this bad?** Not every integer is a unicode scalar value. +/// **Why is this bad?** Not every integer is a Unicode scalar value. /// -/// **Known problems:** None. +/// **Known problems:** +/// - [`from_u32`] which this lint suggests using is slower than `transmute` +/// as it needs to validate the input. +/// If you are certain that the input is always a valid Unicode scalar value, +/// use [`from_u32_unchecked`] which is as fast as `transmute` +/// but has a semantically meaningful name. +/// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`. +/// +/// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html +/// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html /// /// **Example:** /// ```rust /// let _: char = std::mem::transmute(x); // where x: u32 /// // should be: -/// let _: Option = std::char::from_u32(x); +/// let _ = std::char::from_u32(x).unwrap(); /// ``` declare_lint! { pub TRANSMUTE_INT_TO_CHAR, @@ -96,6 +105,33 @@ declare_lint! { "transmutes from an integer to a `char`" } +/// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`. +/// +/// **Why is this bad?** Not every byte slice is a valid UTF-8 string. +/// +/// **Known problems:** +/// - [`from_utf8`] which this lint suggests using is slower than `transmute` +/// as it needs to validate the input. +/// If you are certain that the input is always a valid UTF-8, +/// use [`from_utf8_unchecked`] which is as fast as `transmute` +/// but has a semantically meaningful name. +/// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`. +/// +/// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html +/// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html +/// +/// **Example:** +/// ```rust +/// let _: &str = std::mem::transmute(b); // where b: &[u8] +/// // should be: +/// let _ = std::str::from_utf8(b).unwrap(); +/// ``` +declare_lint! { + pub TRANSMUTE_BYTES_TO_STR, + Warn, + "transmutes from a `&[u8]` to a `&str`" +} + /// **What it does:** Checks for transmutes from an integer to a `bool`. /// /// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`. @@ -142,6 +178,7 @@ impl LintPass for Transmute { USELESS_TRANSMUTE, WRONG_TRANSMUTE, TRANSMUTE_INT_TO_CHAR, + TRANSMUTE_BYTES_TO_STR, TRANSMUTE_INT_TO_BOOL, TRANSMUTE_INT_TO_FLOAT ) @@ -254,9 +291,41 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute { } else { arg }; - db.span_suggestion(e.span, "consider using", format!("std::char::from_u32({})", arg.to_string())); + db.span_suggestion(e.span, "consider using", format!("std::char::from_u32({}).unwrap()", arg.to_string())); } ), + (&ty::TyRef(_, ref ref_from), &ty::TyRef(_, ref ref_to)) => { + if_chain! { + if let (&ty::TySlice(slice_ty), &ty::TyStr) = (&ref_from.ty.sty, &ref_to.ty.sty); + if let ty::TyUint(ast::UintTy::U8) = slice_ty.sty; + if ref_from.mutbl == ref_to.mutbl; + then { + let postfix = if ref_from.mutbl == Mutability::MutMutable { + "_mut" + } else { + "" + }; + + span_lint_and_then( + cx, + TRANSMUTE_BYTES_TO_STR, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |db| { + db.span_suggestion( + e.span, + "consider using", + format!( + "std::str::from_utf8{}({}).unwrap()", + postfix, + snippet(cx, args[0].span, ".."), + ), + ); + } + ) + } + } + }, (&ty::TyInt(ast::IntTy::I8), &ty::TyBool) | (&ty::TyUint(ast::UintTy::U8), &ty::TyBool) => span_lint_and_then( cx, diff --git a/tests/ui/transmute.rs b/tests/ui/transmute.rs index 81582b5a15f..b04297f01fb 100644 --- a/tests/ui/transmute.rs +++ b/tests/ui/transmute.rs @@ -135,4 +135,9 @@ fn int_to_float() { let _: f32 = unsafe { std::mem::transmute(0_i32) }; } +fn bytes_to_str(b: &[u8], mb: &mut [u8]) { + let _: &str = unsafe { std::mem::transmute(b) }; + let _: &mut str = unsafe { std::mem::transmute(mb) }; +} + fn main() { } diff --git a/tests/ui/transmute.stderr b/tests/ui/transmute.stderr index c81ec5260be..6504f55845d 100644 --- a/tests/ui/transmute.stderr +++ b/tests/ui/transmute.stderr @@ -158,7 +158,7 @@ error: transmute from a `u32` to a `char` --> $DIR/transmute.rs:123:28 | 123 | let _: char = unsafe { std::mem::transmute(0_u32) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_u32)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_u32).unwrap()` | = note: `-D transmute-int-to-char` implied by `-D warnings` @@ -166,7 +166,7 @@ error: transmute from a `i32` to a `char` --> $DIR/transmute.rs:124:28 | 124 | let _: char = unsafe { std::mem::transmute(0_i32) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_i32 as u32)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_i32 as u32).unwrap()` error: transmute from a `u8` to a `bool` --> $DIR/transmute.rs:129:28 @@ -190,3 +190,17 @@ error: transmute from a `i32` to a `f32` 135 | let _: f32 = unsafe { std::mem::transmute(0_i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_i32 as u32)` +error: transmute from a `&[u8]` to a `&str` + --> $DIR/transmute.rs:139:28 + | +139 | let _: &str = unsafe { std::mem::transmute(b) }; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(b).unwrap()` + | + = note: `-D transmute-bytes-to-str` implied by `-D warnings` + +error: transmute from a `&mut [u8]` to a `&mut str` + --> $DIR/transmute.rs:140:32 + | +140 | let _: &mut str = unsafe { std::mem::transmute(mb) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()` +