Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions clippy_lints/src/transmute/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<&i32>, 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`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
114 changes: 114 additions & 0 deletions clippy_lints/src/transmute/transmute_adt_argument.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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, AliasTy, GenericArg, GenericArgKind, Ty};

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
let mut found = vec![];

walk_ty(&mut found, from_ty, to_ty);
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_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}`"));
}
},
);
true
}
}

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.
_ => {},
}
}
36 changes: 36 additions & 0 deletions tests/ui/mutable_adt_argument_transmute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![warn(clippy::mutable_adt_argument_transmute)]

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<Option<&mut String>, ()> =
std::mem::transmute(Result::<Option<&String>, ()>::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));
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
let _: Option<(i32, &mut i32)> = std::mem::transmute(Some((&5i32, &5i32)));
//~^ mutable_adt_argument_transmute
}
}
69 changes: 69 additions & 0 deletions tests/ui/mutable_adt_argument_transmute.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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: 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: transmuting references into their mutable version is unsound
--> tests/ui/mutable_adt_argument_transmute.rs:10:13
|
LL | std::mem::transmute(Result::<Option<&String>, ()>::Ok(Some(&"foo".to_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
|
LL | let _: Result<&mut f32, &usize> = std::mem::transmute(Result::<&f32, &usize>::Ok(&2f32));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= 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
|
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: 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: 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