From 84401413eaee598c846f877291f2f5a0515c7275 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Thu, 30 Oct 2025 22:14:14 -0400 Subject: [PATCH 1/8] add lint for transmute from &T to &mut T of a ADT argument --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/transmute/mod.rs | 20 +++++++++++ .../src/transmute/transmute_adt_argument.rs | 35 +++++++++++++++++++ tests/ui/mutable_adt_argument_transmute.rs | 5 +++ 5 files changed, 62 insertions(+) create mode 100644 clippy_lints/src/transmute/transmute_adt_argument.rs create mode 100644 tests/ui/mutable_adt_argument_transmute.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6b3c66e6ec..1747c5790be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7109,6 +7109,7 @@ Released 2018-09-13 [`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut [`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock [`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound +[`mutable_adt_argument_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_adt_argument_transmute [`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type [`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic [`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 51a848d022d8..005726aa50e1 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -740,6 +740,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::transmute::CROSSPOINTER_TRANSMUTE_INFO, crate::transmute::EAGER_TRANSMUTE_INFO, crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS_INFO, + crate::transmute::MUTABLE_ADT_ARGUMENT_TRANSMUTE_INFO, crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO, crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO, crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO, diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 4a150ba098e4..fb268d694257 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -1,6 +1,7 @@ mod crosspointer_transmute; mod eager_transmute; mod missing_transmute_annotations; +mod transmute_adt_argument; mod transmute_int_to_bool; mod transmute_int_to_non_zero; mod transmute_null_to_fn; @@ -153,6 +154,23 @@ declare_clippy_lint! { "warns if a transmute call doesn't have all generics specified" } +declare_clippy_lint! { + /// ### What it does + /// Checks for transmutes between the same adt, where at least one of the type argument goes from &T to &mut T. + /// This is a more specialized version of https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#mutable-transmutes. + /// ### Example + /// + /// ```ignore + /// unsafe { + /// std::mem::transmute::, Option<&mut i32>>(Some(&5)); + /// } + /// ``` + #[clippy::version = "1.95.0"] + pub MUTABLE_ADT_ARGUMENT_TRANSMUTE, + correctness, + "transmutes on the same adt where at least one of the type argument goes from &T to &mut T" +} + declare_clippy_lint! { /// ### What it does /// Checks for transmutes from a `&[u8]` to a `&str`. @@ -472,6 +490,7 @@ impl_lint_pass!(Transmute => [ CROSSPOINTER_TRANSMUTE, EAGER_TRANSMUTE, MISSING_TRANSMUTE_ANNOTATIONS, + MUTABLE_ADT_ARGUMENT_TRANSMUTE, TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, TRANSMUTE_BYTES_TO_STR, TRANSMUTE_INT_TO_BOOL, @@ -551,6 +570,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { let (from_field_ty, from_field_expr) = Self::extract_struct_field(cx, e, from_ty, arg); let linted = wrong_transmute::check(cx, e, from_ty, to_ty) + | transmute_adt_argument::check(cx, e, from_ty, to_ty) | crosspointer_transmute::check(cx, e, from_ty, to_ty) | transmuting_null::check(cx, e, arg, to_ty) | transmute_null_to_fn::check(cx, e, arg, to_ty) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs new file mode 100644 index 000000000000..ecf802d36ba0 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -0,0 +1,35 @@ +use super::MUTABLE_ADT_ARGUMENT_TRANSMUTE; +use clippy_utils::diagnostics::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArgKind, Ty}; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + from_ty + .walk() + .zip(to_ty.walk()) + .filter_map(|(from_ty, to_ty)| { + if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { + Some((from_ty, to_ty)) + } else { + None + } + }) + .filter(|(from_ty_inner, to_ty_inner)| { + if let (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) = (from_ty_inner.kind(), to_ty_inner.kind()) + && from_mut < to_mut + { + span_lint( + cx, + MUTABLE_ADT_ARGUMENT_TRANSMUTE, + e.span, + format!("transmute of type argument {from_ty_inner} to {from_ty_inner}"), + ); + true + } else { + false + } + }) + .count() + > 0 +} diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs new file mode 100644 index 000000000000..de1ab0b35c8a --- /dev/null +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -0,0 +1,5 @@ +#![warn(clippy::mutable_adt_argument_transmute)] + +fn main() { + // test code goes here +} From bb52984b05becd354be12e69431d7a8d2e9e1cda Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Thu, 30 Oct 2025 22:50:33 -0400 Subject: [PATCH 2/8] adding test for mutable_adt_argument_transmute lint --- tests/ui/mutable_adt_argument_transmute.rs | 5 ++++- tests/ui/mutable_adt_argument_transmute.stderr | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/ui/mutable_adt_argument_transmute.stderr diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs index de1ab0b35c8a..357ec3f0220a 100644 --- a/tests/ui/mutable_adt_argument_transmute.rs +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -1,5 +1,8 @@ #![warn(clippy::mutable_adt_argument_transmute)] fn main() { - // test code goes here + unsafe { + let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); + //~^ mutable_adt_argument_transmute + } } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr new file mode 100644 index 000000000000..b17537d0e744 --- /dev/null +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -0,0 +1,11 @@ +error: transmute of type argument &i32 to &i32 + --> tests/ui/mutable_adt_argument_transmute.rs:5:35 + | +LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mutable-adt-argument-transmute` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::mutable_adt_argument_transmute)]` + +error: aborting due to 1 previous error + From 241501719caff704a41e5d63ad7314fd20147f3f Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 31 Oct 2025 09:35:59 -0400 Subject: [PATCH 3/8] more tests for mutable_adt_argument_transmute --- tests/ui/mutable_adt_argument_transmute.rs | 10 +++++++ .../ui/mutable_adt_argument_transmute.stderr | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs index 357ec3f0220a..08f21f4de446 100644 --- a/tests/ui/mutable_adt_argument_transmute.rs +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -4,5 +4,15 @@ fn main() { unsafe { let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); //~^ mutable_adt_argument_transmute + let _: Result<&mut i32, ()> = std::mem::transmute(Result::<&i32, ()>::Ok(&5i32)); + //~^ mutable_adt_argument_transmute + let _: Result, ()> = + std::mem::transmute(Result::, ()>::Ok(Some(&"foo".to_string()))); + //~^ mutable_adt_argument_transmute + let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32)); + //~^ mutable_adt_argument_transmute + let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); + //~^ mutable_adt_argument_transmute + let _: Option<&i32> = std::mem::transmute(Some(&5i32)); } } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index b17537d0e744..6804c15ba78c 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -7,5 +7,29 @@ LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); = note: `-D clippy::mutable-adt-argument-transmute` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutable_adt_argument_transmute)]` -error: aborting due to 1 previous error +error: transmute of type argument &i32 to &i32 + --> tests/ui/mutable_adt_argument_transmute.rs:7:39 + | +LL | let _: Result<&mut i32, ()> = std::mem::transmute(Result::<&i32, ()>::Ok(&5i32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute of type argument &std::string::String to &std::string::String + --> tests/ui/mutable_adt_argument_transmute.rs:10:13 + | +LL | std::mem::transmute(Result::, ()>::Ok(Some(&"foo".to_string()))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute of type argument &f32 to &f32 + --> tests/ui/mutable_adt_argument_transmute.rs:12:43 + | +LL | let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute of type argument &usize to &usize + --> tests/ui/mutable_adt_argument_transmute.rs:14:41 + | +LL | let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors From f542765bebf8d6395c15409f5e950a7e098caa0e Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Thu, 20 Nov 2025 15:46:30 -0500 Subject: [PATCH 4/8] made `transmute_adt_argument` more resilant to transmutes of different adts with different arities --- .../src/transmute/transmute_adt_argument.rs | 79 +++++++++++++------ .../ui/mutable_adt_argument_transmute.stderr | 10 +-- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs index ecf802d36ba0..681334309280 100644 --- a/clippy_lints/src/transmute/transmute_adt_argument.rs +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -5,31 +5,58 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { - from_ty - .walk() - .zip(to_ty.walk()) - .filter_map(|(from_ty, to_ty)| { - if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { - Some((from_ty, to_ty)) - } else { - None + // assumes walk will return all types in the same order + let mut from_ty_walker = from_ty.walk(); + let mut to_ty_walker = to_ty.walk(); + let mut found = false; + while let Some((from_ty, to_ty)) = from_ty_walker.next().zip(to_ty_walker.next()) { + if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { + match (from_ty.kind(), to_ty.kind()) { + (ty::Bool, ty::Bool) + | (ty::Char, ty::Char) + | (ty::Int(_), ty::Int(_)) + | (ty::Uint(_), ty::Uint(_)) + | (ty::Float(_), ty::Float(_)) + | (ty::Foreign(_), ty::Foreign(_)) + | (ty::Str, ty::Str) + | (ty::Array(_, _), ty::Array(_, _)) + | (ty::Pat(_, _), ty::Pat(_, _)) + | (ty::Slice(_), ty::Slice(_)) + | (ty::RawPtr(_, _), ty::RawPtr(_, _)) + | (ty::FnDef(_, _), ty::FnDef(_, _)) + | (ty::FnPtr(_, _), ty::FnPtr(_, _)) + | (ty::UnsafeBinder(_), ty::UnsafeBinder(_)) + | (ty::Dynamic(_, _), ty::Dynamic(_, _)) + | (ty::Closure(_, _), ty::Closure(_, _)) + | (ty::CoroutineClosure(_, _), ty::CoroutineClosure(_, _)) + | (ty::Coroutine(_, _), ty::Coroutine(_, _)) + | (ty::CoroutineWitness(_, _), ty::CoroutineWitness(_, _)) + | (ty::Never, ty::Never) + | (ty::Tuple(_), ty::Tuple(_)) + | (ty::Alias(_, _), ty::Alias(_, _)) + | (ty::Param(_), ty::Param(_)) + | (ty::Bound(_, _), ty::Bound(_, _)) + | (ty::Placeholder(_), ty::Placeholder(_)) + | (ty::Infer(_), ty::Infer(_)) + | (ty::Error(_), ty::Error(_)) => {}, + (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { + if from_mut < to_mut { + span_lint( + cx, + MUTABLE_ADT_ARGUMENT_TRANSMUTE, + e.span, + format!("transmute of type argument {from_ty} to {to_ty}"), + ); + found = true; + } + }, + (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, + _ => { + from_ty_walker.skip_current_subtree(); + to_ty_walker.skip_current_subtree(); + }, } - }) - .filter(|(from_ty_inner, to_ty_inner)| { - if let (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) = (from_ty_inner.kind(), to_ty_inner.kind()) - && from_mut < to_mut - { - span_lint( - cx, - MUTABLE_ADT_ARGUMENT_TRANSMUTE, - e.span, - format!("transmute of type argument {from_ty_inner} to {from_ty_inner}"), - ); - true - } else { - false - } - }) - .count() - > 0 + } + } + found } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index 6804c15ba78c..477bc18ed22c 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -1,4 +1,4 @@ -error: transmute of type argument &i32 to &i32 +error: transmute of type argument &i32 to &mut i32 --> tests/ui/mutable_adt_argument_transmute.rs:5:35 | LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); @@ -7,25 +7,25 @@ LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); = note: `-D clippy::mutable-adt-argument-transmute` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutable_adt_argument_transmute)]` -error: transmute of type argument &i32 to &i32 +error: transmute of type argument &i32 to &mut i32 --> tests/ui/mutable_adt_argument_transmute.rs:7:39 | LL | let _: Result<&mut i32, ()> = std::mem::transmute(Result::<&i32, ()>::Ok(&5i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: transmute of type argument &std::string::String to &std::string::String +error: transmute of type argument &std::string::String to &mut std::string::String --> tests/ui/mutable_adt_argument_transmute.rs:10:13 | LL | std::mem::transmute(Result::, ()>::Ok(Some(&"foo".to_string()))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: transmute of type argument &f32 to &f32 +error: transmute of type argument &f32 to &mut f32 --> tests/ui/mutable_adt_argument_transmute.rs:12:43 | LL | let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: transmute of type argument &usize to &usize +error: transmute of type argument &usize to &mut usize --> tests/ui/mutable_adt_argument_transmute.rs:14:41 | LL | let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); From 5baff1faba49e3deefcdfcf2f3b05f5e5bb18e76 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Sun, 18 Jan 2026 12:48:10 -0500 Subject: [PATCH 5/8] made show all caught cases for a single expression together --- .../src/transmute/transmute_adt_argument.rs | 30 ++++++++++++------- tests/ui/mutable_adt_argument_transmute.rs | 2 ++ .../ui/mutable_adt_argument_transmute.stderr | 30 +++++++++++++++---- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs index 681334309280..f5cf9066bfb3 100644 --- a/clippy_lints/src/transmute/transmute_adt_argument.rs +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -1,5 +1,5 @@ use super::MUTABLE_ADT_ARGUMENT_TRANSMUTE; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_then; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; @@ -8,7 +8,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty // assumes walk will return all types in the same order let mut from_ty_walker = from_ty.walk(); let mut to_ty_walker = to_ty.walk(); - let mut found = false; + let mut found = vec![]; while let Some((from_ty, to_ty)) = from_ty_walker.next().zip(to_ty_walker.next()) { if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { match (from_ty.kind(), to_ty.kind()) { @@ -41,13 +41,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty | (ty::Error(_), ty::Error(_)) => {}, (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { if from_mut < to_mut { - span_lint( - cx, - MUTABLE_ADT_ARGUMENT_TRANSMUTE, - e.span, - format!("transmute of type argument {from_ty} to {to_ty}"), - ); - found = true; + found.push((from_ty, to_ty)); } }, (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, @@ -58,5 +52,21 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty } } } - found + if found.is_empty() { + false + } else { + span_lint_and_then( + cx, + MUTABLE_ADT_ARGUMENT_TRANSMUTE, + e.span, + "transmuting references into their mutable version is unsound", + |diag| { + found.dedup_by(|ty1, ty2| ty1.1 == ty2.1); + for (from_ty, to_ty) in found { + diag.note(format!("transmute of type argument {from_ty} to {to_ty}")); + } + }, + ); + true + } } diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs index 08f21f4de446..fd3894a5c40e 100644 --- a/tests/ui/mutable_adt_argument_transmute.rs +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -14,5 +14,7 @@ fn main() { let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); //~^ mutable_adt_argument_transmute let _: Option<&i32> = std::mem::transmute(Some(&5i32)); + let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32))); + //~^ mutable_adt_argument_transmute } } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index 477bc18ed22c..6a021bccfdd9 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -1,35 +1,53 @@ -error: transmute of type argument &i32 to &mut i32 +error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:5:35 | LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: transmute of type argument &i32 to &mut i32 = note: `-D clippy::mutable-adt-argument-transmute` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutable_adt_argument_transmute)]` -error: transmute of type argument &i32 to &mut i32 +error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:7:39 | LL | let _: Result<&mut i32, ()> = std::mem::transmute(Result::<&i32, ()>::Ok(&5i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &i32 to &mut i32 -error: transmute of type argument &std::string::String to &mut std::string::String +error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:10:13 | LL | std::mem::transmute(Result::, ()>::Ok(Some(&"foo".to_string()))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &std::string::String to &mut std::string::String -error: transmute of type argument &f32 to &mut f32 +error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:12:43 | LL | let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &f32 to &mut f32 -error: transmute of type argument &usize to &mut usize +error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:14:41 | LL | let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &usize to &mut usize + +error: transmuting references into their mutable version is unsound + --> tests/ui/mutable_adt_argument_transmute.rs:17:57 + | +LL | let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &i32 to &mut i32 + = note: transmute of type argument &u32 to &mut u32 -error: aborting due to 5 previous errors +error: aborting due to 6 previous errors From aa8d796846191f1d7b5d5b126c9dc50666186eef Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Sat, 21 Feb 2026 23:37:29 -0500 Subject: [PATCH 6/8] made `transmute_adt_argument` special case `fn` and closures where only the return type matter for transmute --- .../src/transmute/transmute_adt_argument.rs | 57 ++++++++++++++++--- tests/ui/mutable_adt_argument_transmute.rs | 14 +++++ .../ui/mutable_adt_argument_transmute.stderr | 10 +++- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs index f5cf9066bfb3..3bbff3df0013 100644 --- a/clippy_lints/src/transmute/transmute_adt_argument.rs +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -2,13 +2,15 @@ use super::MUTABLE_ADT_ARGUMENT_TRANSMUTE; use clippy_utils::diagnostics::span_lint_and_then; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgKind, Ty}; +use rustc_middle::ty::walk::TypeWalker; +use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { // assumes walk will return all types in the same order let mut from_ty_walker = from_ty.walk(); let mut to_ty_walker = to_ty.walk(); let mut found = vec![]; + while let Some((from_ty, to_ty)) = from_ty_walker.next().zip(to_ty_walker.next()) { if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { match (from_ty.kind(), to_ty.kind()) { @@ -23,28 +25,43 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty | (ty::Pat(_, _), ty::Pat(_, _)) | (ty::Slice(_), ty::Slice(_)) | (ty::RawPtr(_, _), ty::RawPtr(_, _)) - | (ty::FnDef(_, _), ty::FnDef(_, _)) - | (ty::FnPtr(_, _), ty::FnPtr(_, _)) | (ty::UnsafeBinder(_), ty::UnsafeBinder(_)) + | (ty::Alias(_, _), ty::Alias(_, _)) | (ty::Dynamic(_, _), ty::Dynamic(_, _)) - | (ty::Closure(_, _), ty::Closure(_, _)) - | (ty::CoroutineClosure(_, _), ty::CoroutineClosure(_, _)) - | (ty::Coroutine(_, _), ty::Coroutine(_, _)) | (ty::CoroutineWitness(_, _), ty::CoroutineWitness(_, _)) | (ty::Never, ty::Never) | (ty::Tuple(_), ty::Tuple(_)) - | (ty::Alias(_, _), ty::Alias(_, _)) | (ty::Param(_), ty::Param(_)) | (ty::Bound(_, _), ty::Bound(_, _)) | (ty::Placeholder(_), ty::Placeholder(_)) | (ty::Infer(_), ty::Infer(_)) | (ty::Error(_), ty::Error(_)) => {}, + (ty::Closure(_, from_r), ty::Closure(_, to_r)) => { + let from_r = from_r.as_closure().sig(); + let to_r = to_r.as_closure().sig(); + skip_fn_parameters( + &mut from_ty_walker, + &mut to_ty_walker, + from_r.inputs().iter(), + to_r.inputs().iter(), + ); + }, + (ty::FnPtr(from_r, _), ty::FnPtr(to_r, _)) => { + skip_fn_parameters( + &mut from_ty_walker, + &mut to_ty_walker, + from_r.inputs().iter(), + to_r.inputs().iter(), + ); + }, (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { if from_mut < to_mut { found.push((from_ty, to_ty)); } }, (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, + // for coroutines we they don't return anything so we just skip them (ty::CoroutineClosure(_, _), + // ty::CoroutineClosure(_, _)) | (ty::Coroutine(_, _), ty::Coroutine(_, _)) _ => { from_ty_walker.skip_current_subtree(); to_ty_walker.skip_current_subtree(); @@ -70,3 +87,29 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty true } } + +fn skip_fn_parameters( + from_ty_walker: &mut TypeWalker>, + to_ty_walker: &mut TypeWalker>, + from_r: impl Iterator, + to_r: impl Iterator, +) { + if from_r + .map(|_| { + from_ty_walker.next(); + from_ty_walker.skip_current_subtree(); + }) + .count() + != to_r + .map(|_| { + to_ty_walker.next(); + to_ty_walker.skip_current_subtree(); + }) + .count() + { + from_ty_walker.next(); + from_ty_walker.skip_current_subtree(); + to_ty_walker.next(); + to_ty_walker.skip_current_subtree(); + } +} diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs index fd3894a5c40e..790c4d5496ba 100644 --- a/tests/ui/mutable_adt_argument_transmute.rs +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -16,5 +16,19 @@ fn main() { let _: Option<&i32> = std::mem::transmute(Some(&5i32)); let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32))); //~^ mutable_adt_argument_transmute + // This one is not dangerous, as the function will not try + // to mutate its parameter anyway. + fn f(x: &i32) {} + let a: fn(&i32) = f; + let b: fn(&mut u32) = std::mem::transmute(a); + + // This one should be linted because it would not be safe + // to assume that the return value can be mutated. + fn g() -> &'static i32 { + &0 + } + let c: fn() -> &'static i32 = g; + let d: fn() -> &'static mut i32 = std::mem::transmute(c); + //~^ mutable_adt_argument_transmute } } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index 6a021bccfdd9..874cddcc8b10 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -49,5 +49,13 @@ LL | let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute = note: transmute of type argument &i32 to &mut i32 = note: transmute of type argument &u32 to &mut u32 -error: aborting due to 6 previous errors +error: transmuting references into their mutable version is unsound + --> tests/ui/mutable_adt_argument_transmute.rs:31:43 + | +LL | let d: fn() -> &'static mut i32 = std::mem::transmute(c); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument &i32 to &mut i32 + +error: aborting due to 7 previous errors From c850b88c1ec3395ae066bca6b575a0aad8bdf284 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Sat, 7 Mar 2026 23:35:25 -0500 Subject: [PATCH 7/8] `transmute_adt_argument` added some comments, simplified some code --- .../src/transmute/transmute_adt_argument.rs | 119 +++++++++--------- .../ui/mutable_adt_argument_transmute.stderr | 16 +-- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs index 3bbff3df0013..93a6047509c3 100644 --- a/clippy_lints/src/transmute/transmute_adt_argument.rs +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -12,61 +12,66 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty let mut found = vec![]; while let Some((from_ty, to_ty)) = from_ty_walker.next().zip(to_ty_walker.next()) { - if let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) { - match (from_ty.kind(), to_ty.kind()) { - (ty::Bool, ty::Bool) - | (ty::Char, ty::Char) - | (ty::Int(_), ty::Int(_)) - | (ty::Uint(_), ty::Uint(_)) - | (ty::Float(_), ty::Float(_)) - | (ty::Foreign(_), ty::Foreign(_)) - | (ty::Str, ty::Str) - | (ty::Array(_, _), ty::Array(_, _)) - | (ty::Pat(_, _), ty::Pat(_, _)) - | (ty::Slice(_), ty::Slice(_)) - | (ty::RawPtr(_, _), ty::RawPtr(_, _)) - | (ty::UnsafeBinder(_), ty::UnsafeBinder(_)) - | (ty::Alias(_, _), ty::Alias(_, _)) - | (ty::Dynamic(_, _), ty::Dynamic(_, _)) - | (ty::CoroutineWitness(_, _), ty::CoroutineWitness(_, _)) - | (ty::Never, ty::Never) - | (ty::Tuple(_), ty::Tuple(_)) - | (ty::Param(_), ty::Param(_)) - | (ty::Bound(_, _), ty::Bound(_, _)) - | (ty::Placeholder(_), ty::Placeholder(_)) - | (ty::Infer(_), ty::Infer(_)) - | (ty::Error(_), ty::Error(_)) => {}, - (ty::Closure(_, from_r), ty::Closure(_, to_r)) => { - let from_r = from_r.as_closure().sig(); - let to_r = to_r.as_closure().sig(); - skip_fn_parameters( - &mut from_ty_walker, - &mut to_ty_walker, - from_r.inputs().iter(), - to_r.inputs().iter(), - ); - }, - (ty::FnPtr(from_r, _), ty::FnPtr(to_r, _)) => { - skip_fn_parameters( - &mut from_ty_walker, - &mut to_ty_walker, - from_r.inputs().iter(), - to_r.inputs().iter(), - ); - }, - (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { - if from_mut < to_mut { - found.push((from_ty, to_ty)); - } - }, - (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, - // for coroutines we they don't return anything so we just skip them (ty::CoroutineClosure(_, _), - // ty::CoroutineClosure(_, _)) | (ty::Coroutine(_, _), ty::Coroutine(_, _)) - _ => { - from_ty_walker.skip_current_subtree(); - to_ty_walker.skip_current_subtree(); - }, - } + let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) else { + continue; + }; + match (from_ty.kind(), to_ty.kind()) { + (ty::Bool, ty::Bool) + | (ty::Char, ty::Char) + | (ty::Int(_), ty::Int(_)) + | (ty::Uint(_), ty::Uint(_)) + | (ty::Float(_), ty::Float(_)) + | (ty::Foreign(_), ty::Foreign(_)) + | (ty::Str, ty::Str) + | (ty::Array(_, _), ty::Array(_, _)) + | (ty::Pat(_, _), ty::Pat(_, _)) + | (ty::Slice(_), ty::Slice(_)) + | (ty::RawPtr(_, _), ty::RawPtr(_, _)) + | (ty::UnsafeBinder(_), ty::UnsafeBinder(_)) + | (ty::Alias(_, _), ty::Alias(_, _)) + | (ty::Dynamic(_, _), ty::Dynamic(_, _)) + | (ty::CoroutineWitness(_, _), ty::CoroutineWitness(_, _)) + | (ty::Never, ty::Never) + | (ty::Tuple(_), ty::Tuple(_)) + | (ty::Param(_), ty::Param(_)) + | (ty::Bound(_, _), ty::Bound(_, _)) + | (ty::Placeholder(_), ty::Placeholder(_)) + | (ty::Infer(_), ty::Infer(_)) + | (ty::Error(_), ty::Error(_)) => {}, + // it's safe to transmute the parameters of functions/closure + (ty::Closure(_, from_r), ty::Closure(_, to_r)) => { + let from_r = from_r.as_closure().sig(); + let to_r = to_r.as_closure().sig(); + skip_fn_parameters( + &mut from_ty_walker, + &mut to_ty_walker, + from_r.inputs().iter(), + to_r.inputs().iter(), + ); + }, + // it's safe to transmute the parameters of functions/closure + (ty::FnPtr(from_r, _), ty::FnPtr(to_r, _)) => { + skip_fn_parameters( + &mut from_ty_walker, + &mut to_ty_walker, + from_r.inputs().iter(), + to_r.inputs().iter(), + ); + }, + (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { + if from_mut < to_mut { + found.push((from_ty, to_ty)); + } + }, + (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, + // for coroutines we they don't return anything so we just skip them, since it's safe to transmute the + // parameters of functions/closure, and coroutines do not have return types. + // (ty::CoroutineClosure(_, _), + // ty::CoroutineClosure(_, _)) | (ty::Coroutine(_, _), ty::Coroutine(_, _)) + _ => { + from_ty_walker.skip_current_subtree(); + to_ty_walker.skip_current_subtree(); + }, } } if found.is_empty() { @@ -78,9 +83,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty e.span, "transmuting references into their mutable version is unsound", |diag| { - found.dedup_by(|ty1, ty2| ty1.1 == ty2.1); + found.dedup_by_key(|from_ty| from_ty.1); for (from_ty, to_ty) in found { - diag.note(format!("transmute of type argument {from_ty} to {to_ty}")); + diag.note(format!("transmute of type argument `{from_ty}` to `{to_ty}`")); } }, ); diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index 874cddcc8b10..df98ee741be5 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -4,7 +4,7 @@ error: transmuting references into their mutable version is unsound LL | let _: Option<&mut i32> = std::mem::transmute(Some(&5i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &i32 to &mut i32 + = note: transmute of type argument `&i32` to `&mut i32` = note: `-D clippy::mutable-adt-argument-transmute` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mutable_adt_argument_transmute)]` @@ -14,7 +14,7 @@ error: transmuting references into their mutable version is unsound LL | let _: Result<&mut i32, ()> = std::mem::transmute(Result::<&i32, ()>::Ok(&5i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &i32 to &mut i32 + = note: transmute of type argument `&i32` to `&mut i32` error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:10:13 @@ -22,7 +22,7 @@ error: transmuting references into their mutable version is unsound LL | std::mem::transmute(Result::, ()>::Ok(Some(&"foo".to_string()))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &std::string::String to &mut std::string::String + = note: transmute of type argument `&std::string::String` to `&mut std::string::String` error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:12:43 @@ -30,7 +30,7 @@ error: transmuting references into their mutable version is unsound LL | let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &f32 to &mut f32 + = note: transmute of type argument `&f32` to `&mut f32` error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:14:41 @@ -38,7 +38,7 @@ error: transmuting references into their mutable version is unsound LL | let _: Result<(), &mut usize> = std::mem::transmute(Result::<(), &usize>::Ok(())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &usize to &mut usize + = note: transmute of type argument `&usize` to `&mut usize` error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:17:57 @@ -46,8 +46,8 @@ error: transmuting references into their mutable version is unsound LL | let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &i32 to &mut i32 - = note: transmute of type argument &u32 to &mut u32 + = note: transmute of type argument `&i32` to `&mut i32` + = note: transmute of type argument `&u32` to `&mut u32` error: transmuting references into their mutable version is unsound --> tests/ui/mutable_adt_argument_transmute.rs:31:43 @@ -55,7 +55,7 @@ error: transmuting references into their mutable version is unsound LL | let d: fn() -> &'static mut i32 = std::mem::transmute(c); | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: transmute of type argument &i32 to &mut i32 + = note: transmute of type argument `&i32` to `&mut i32` error: aborting due to 7 previous errors From 7ac78442fe4e58180b0989b087776709fb06ea49 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Sun, 8 Mar 2026 16:59:16 -0400 Subject: [PATCH 8/8] `transmute_adt_arguement` moved away from `ty::walk` --- .../src/transmute/transmute_adt_argument.rs | 176 +++++++++--------- tests/ui/mutable_adt_argument_transmute.rs | 2 + .../ui/mutable_adt_argument_transmute.stderr | 10 +- 3 files changed, 96 insertions(+), 92 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_adt_argument.rs b/clippy_lints/src/transmute/transmute_adt_argument.rs index 93a6047509c3..a219d4b5f167 100644 --- a/clippy_lints/src/transmute/transmute_adt_argument.rs +++ b/clippy_lints/src/transmute/transmute_adt_argument.rs @@ -2,78 +2,12 @@ use super::MUTABLE_ADT_ARGUMENT_TRANSMUTE; use clippy_utils::diagnostics::span_lint_and_then; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::walk::TypeWalker; -use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt}; +use rustc_middle::ty::{self, AliasTy, GenericArg, GenericArgKind, Ty}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { - // assumes walk will return all types in the same order - let mut from_ty_walker = from_ty.walk(); - let mut to_ty_walker = to_ty.walk(); let mut found = vec![]; - while let Some((from_ty, to_ty)) = from_ty_walker.next().zip(to_ty_walker.next()) { - let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) else { - continue; - }; - match (from_ty.kind(), to_ty.kind()) { - (ty::Bool, ty::Bool) - | (ty::Char, ty::Char) - | (ty::Int(_), ty::Int(_)) - | (ty::Uint(_), ty::Uint(_)) - | (ty::Float(_), ty::Float(_)) - | (ty::Foreign(_), ty::Foreign(_)) - | (ty::Str, ty::Str) - | (ty::Array(_, _), ty::Array(_, _)) - | (ty::Pat(_, _), ty::Pat(_, _)) - | (ty::Slice(_), ty::Slice(_)) - | (ty::RawPtr(_, _), ty::RawPtr(_, _)) - | (ty::UnsafeBinder(_), ty::UnsafeBinder(_)) - | (ty::Alias(_, _), ty::Alias(_, _)) - | (ty::Dynamic(_, _), ty::Dynamic(_, _)) - | (ty::CoroutineWitness(_, _), ty::CoroutineWitness(_, _)) - | (ty::Never, ty::Never) - | (ty::Tuple(_), ty::Tuple(_)) - | (ty::Param(_), ty::Param(_)) - | (ty::Bound(_, _), ty::Bound(_, _)) - | (ty::Placeholder(_), ty::Placeholder(_)) - | (ty::Infer(_), ty::Infer(_)) - | (ty::Error(_), ty::Error(_)) => {}, - // it's safe to transmute the parameters of functions/closure - (ty::Closure(_, from_r), ty::Closure(_, to_r)) => { - let from_r = from_r.as_closure().sig(); - let to_r = to_r.as_closure().sig(); - skip_fn_parameters( - &mut from_ty_walker, - &mut to_ty_walker, - from_r.inputs().iter(), - to_r.inputs().iter(), - ); - }, - // it's safe to transmute the parameters of functions/closure - (ty::FnPtr(from_r, _), ty::FnPtr(to_r, _)) => { - skip_fn_parameters( - &mut from_ty_walker, - &mut to_ty_walker, - from_r.inputs().iter(), - to_r.inputs().iter(), - ); - }, - (ty::Ref(_, _, from_mut), ty::Ref(_, _, to_mut)) => { - if from_mut < to_mut { - found.push((from_ty, to_ty)); - } - }, - (ty::Adt(adt1, _), ty::Adt(adt2, _)) if adt1 == adt2 => {}, - // for coroutines we they don't return anything so we just skip them, since it's safe to transmute the - // parameters of functions/closure, and coroutines do not have return types. - // (ty::CoroutineClosure(_, _), - // ty::CoroutineClosure(_, _)) | (ty::Coroutine(_, _), ty::Coroutine(_, _)) - _ => { - from_ty_walker.skip_current_subtree(); - to_ty_walker.skip_current_subtree(); - }, - } - } + walk_ty(&mut found, from_ty, to_ty); if found.is_empty() { false } else { @@ -93,28 +27,88 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty } } -fn skip_fn_parameters( - from_ty_walker: &mut TypeWalker>, - to_ty_walker: &mut TypeWalker>, - from_r: impl Iterator, - to_r: impl Iterator, -) { - if from_r - .map(|_| { - from_ty_walker.next(); - from_ty_walker.skip_current_subtree(); - }) - .count() - != to_r - .map(|_| { - to_ty_walker.next(); - to_ty_walker.skip_current_subtree(); - }) - .count() - { - from_ty_walker.next(); - from_ty_walker.skip_current_subtree(); - to_ty_walker.next(); - to_ty_walker.skip_current_subtree(); +fn walk_arg<'tcx>(found: &mut Vec<(Ty<'tcx>, Ty<'tcx>)>, from_ty: GenericArg<'tcx>, to_ty: GenericArg<'tcx>) { + let (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) = (from_ty.kind(), to_ty.kind()) else { + return; + }; + walk_ty(found, from_ty, to_ty); +} +fn walk_ty<'tcx>(found: &mut Vec<(Ty<'tcx>, Ty<'tcx>)>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) { + match (from_ty.kind(), to_ty.kind()) { + (ty::RawPtr(from_ty, _), ty::RawPtr(to_ty, _)) + | (ty::Slice(from_ty), ty::Slice(to_ty)) + | (ty::Array(from_ty, _), ty::Array(to_ty, _)) + | (ty::Pat(from_ty, _), ty::Pat(to_ty, _)) => { + walk_ty(found, *from_ty, *to_ty); + }, + + (ty::UnsafeBinder(from_ty), ty::UnsafeBinder(to_ty)) => { + walk_ty(found, from_ty.skip_binder(), to_ty.skip_binder()); + }, + (ty::Alias(AliasTy { args: from_tys, .. }), ty::Alias(AliasTy { args: to_tys, .. })) => { + from_tys + .iter() + .zip(to_tys.iter()) + .for_each(|(from_ty, to_ty)| walk_arg(found, from_ty, to_ty)); + }, + (ty::Dynamic(from_tys, _), ty::Dynamic(to_tys, _)) => { + from_tys.iter().zip(to_tys.iter()).for_each(|(from_ty, to_ty)| { + let (args, opt_tys) = match (from_ty.skip_binder(), to_ty.skip_binder()) { + (ty::ExistentialPredicate::Trait(from_trait), ty::ExistentialPredicate::Trait(to_trait)) => { + (from_trait.args.iter().zip(to_trait.args.iter()), None) + }, + (ty::ExistentialPredicate::Projection(from_p), ty::ExistentialPredicate::Projection(to_p)) => { + (from_p.args.iter().zip(to_p.args.iter()), Some((from_p.term, to_p.term))) + }, + _ => return, + }; + + args.chain( + opt_tys.and_then(|(from_term, to_term)| match (from_term.kind(), to_term.kind()) { + (ty::TermKind::Ty(from_ty), ty::TermKind::Ty(to_ty)) => Some((from_ty.into(), to_ty.into())), + _ => None, + }), + ) + .for_each(|(from_ty, to_ty)| walk_arg(found, from_ty, to_ty)); + }); + }, + + (ty::Tuple(from_tys), ty::Tuple(to_tys)) => { + from_tys + .iter() + .zip(to_tys.iter()) + .for_each(|(from_ty, to_ty)| walk_ty(found, from_ty, to_ty)); + }, + // it's safe to transmute the parameters of functions/closure + (ty::Closure(_, from_r), ty::Closure(_, to_r)) => { + let from_ty = from_r.as_closure().sig().output(); + let to_ty = to_r.as_closure().sig().output(); + walk_ty(found, from_ty.skip_binder(), to_ty.skip_binder()); + }, + // it's safe to transmute the parameters of functions/closure + (ty::FnPtr(from_r, _), ty::FnPtr(to_r, _)) => { + walk_ty(found, from_r.output().skip_binder(), to_r.output().skip_binder()); + }, + (ty::Ref(_, from_ty_ref, from_mut), ty::Ref(_, to_ty_ref, to_mut)) => { + if from_mut < to_mut { + found.push((from_ty, to_ty)); + } + walk_ty(found, *from_ty_ref, *to_ty_ref); + }, + (ty::CoroutineWitness(_, from_tys), ty::CoroutineWitness(_, to_tys)) => { + from_tys + .iter() + .zip(to_tys.iter()) + .for_each(|(from_ty, to_ty)| walk_arg(found, from_ty, to_ty)); + }, + (ty::Adt(adt1, from_tys), ty::Adt(adt2, to_tys)) if adt1 == adt2 => { + from_tys + .iter() + .zip(to_tys.iter()) + .for_each(|(from_ty, to_ty)| walk_arg(found, from_ty, to_ty)); + }, + // for coroutines we they don't return anything so we just skip them, since it's safe to transmute the + // parameters of functions/closure, and coroutines do not have return types. + _ => {}, } } diff --git a/tests/ui/mutable_adt_argument_transmute.rs b/tests/ui/mutable_adt_argument_transmute.rs index 790c4d5496ba..4930cdc3ba37 100644 --- a/tests/ui/mutable_adt_argument_transmute.rs +++ b/tests/ui/mutable_adt_argument_transmute.rs @@ -30,5 +30,7 @@ fn main() { let c: fn() -> &'static i32 = g; let d: fn() -> &'static mut i32 = std::mem::transmute(c); //~^ mutable_adt_argument_transmute + let _: Option<(i32, &mut i32)> = std::mem::transmute(Some((&5i32, &5i32))); + //~^ mutable_adt_argument_transmute } } diff --git a/tests/ui/mutable_adt_argument_transmute.stderr b/tests/ui/mutable_adt_argument_transmute.stderr index df98ee741be5..8fdb04ec2376 100644 --- a/tests/ui/mutable_adt_argument_transmute.stderr +++ b/tests/ui/mutable_adt_argument_transmute.stderr @@ -57,5 +57,13 @@ LL | let d: fn() -> &'static mut i32 = std::mem::transmute(c); | = note: transmute of type argument `&i32` to `&mut i32` -error: aborting due to 7 previous errors +error: transmuting references into their mutable version is unsound + --> tests/ui/mutable_adt_argument_transmute.rs:33:42 + | +LL | let _: Option<(i32, &mut i32)> = std::mem::transmute(Some((&5i32, &5i32))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: transmute of type argument `&i32` to `&mut i32` + +error: aborting due to 8 previous errors