mod transmute_ptr_to_ptr;
mod transmute_ptr_to_ref;
mod transmute_ref_to_ref;
+mod transmute_undefined_repr;
mod transmutes_expressible_as_ptr_casts;
mod unsound_collection_transmute;
mod useless_transmute;
"transmute between collections of layout-incompatible types"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes either to or from a type which does not have a defined representation.
+ ///
+ /// ### Why is this bad?
+ /// The results of such a transmute are not defined.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo<T>(u32, T);
+ /// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// struct Foo<T>(u32, T);
+ /// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub TRANSMUTE_UNDEFINED_REPR,
+ correctness,
+ "transmute to or from a type with an undefined representation"
+}
+
declare_lint_pass!(Transmute => [
CROSSPOINTER_TRANSMUTE,
TRANSMUTE_PTR_TO_REF,
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ TRANSMUTE_UNDEFINED_REPR,
]);
impl<'tcx> LateLintPass<'tcx> for Transmute {
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
- | unsound_collection_transmute::check(cx, e, from_ty, to_ty);
+ | (
+ unsound_collection_transmute::check(cx, e, from_ty, to_ty)
+ || transmute_undefined_repr::check(cx, e, from_ty, to_ty)
+ );
if !linted {
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
--- /dev/null
+use super::TRANSMUTE_UNDEFINED_REPR;
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::subst::{GenericArg, Subst};
+use rustc_middle::ty::{self, Ty, TyS, TypeAndMut};
+use rustc_span::Span;
+
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty_orig: Ty<'tcx>,
+ to_ty_orig: Ty<'tcx>,
+) -> bool {
+ let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
+ let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
+
+ while !TyS::same_type(from_ty, to_ty) {
+ match reduce_refs(cx, e.span, from_ty, to_ty) {
+ ReducedTys::FromFatPtr { unsized_ty, .. } => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
+ |diag| {
+ if !TyS::same_type(from_ty_orig.peel_refs(), unsized_ty) {
+ diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
+ }
+ },
+ );
+ return true;
+ },
+ ReducedTys::ToFatPtr { unsized_ty, .. } => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
+ |diag| {
+ if !TyS::same_type(to_ty_orig.peel_refs(), unsized_ty) {
+ diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
+ }
+ },
+ );
+ return true;
+ },
+ ReducedTys::ToPtr {
+ from_ty: from_sub_ty,
+ to_ty: to_sub_ty,
+ } => match reduce_ty(cx, from_sub_ty) {
+ ReducedTy::UnorderedFields(from_ty) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
+ |diag| {
+ if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
+ }
+ },
+ );
+ return true;
+ },
+ ReducedTy::Ref(from_sub_ty) => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
+ },
+ _ => break,
+ },
+ ReducedTys::FromPtr {
+ from_ty: from_sub_ty,
+ to_ty: to_sub_ty,
+ } => match reduce_ty(cx, to_sub_ty) {
+ ReducedTy::UnorderedFields(to_ty) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
+ |diag| {
+ if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
+ }
+ },
+ );
+ return true;
+ },
+ ReducedTy::Ref(to_sub_ty) => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
+ },
+ _ => break,
+ },
+ ReducedTys::Other {
+ from_ty: from_sub_ty,
+ to_ty: to_sub_ty,
+ } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
+ (ReducedTy::IntArray, _) | (_, ReducedTy::IntArray) => return false,
+ (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty))
+ if !TyS::same_type(from_ty, to_ty) =>
+ {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!(
+ "transmute from `{}` to `{}`, both of which have an undefined layout",
+ from_ty_orig, to_ty_orig
+ ),
+ |diag| {
+ if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def())
+ && from_def == to_def
+ {
+ diag.note(&format!(
+ "two instances of the same generic type (`{}`) may have different layouts",
+ cx.tcx.item_name(from_def.did)
+ ));
+ } else {
+ if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
+ }
+ if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
+ }
+ }
+ },
+ );
+ return true;
+ },
+ (
+ ReducedTy::UnorderedFields(from_ty),
+ ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
+ |diag| {
+ if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
+ }
+ },
+ );
+ return true;
+ },
+ (
+ ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
+ ReducedTy::UnorderedFields(to_ty),
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
+ &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
+ |diag| {
+ if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
+ diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
+ }
+ },
+ );
+ return true;
+ },
+ (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
+ },
+ (
+ ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
+ ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
+ )
+ | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break,
+ },
+ }
+ }
+
+ false
+}
+
+enum ReducedTys<'tcx> {
+ FromFatPtr { unsized_ty: Ty<'tcx> },
+ ToFatPtr { unsized_ty: Ty<'tcx> },
+ ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
+ FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
+ Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
+}
+
+fn reduce_refs<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ mut from_ty: Ty<'tcx>,
+ mut to_ty: Ty<'tcx>,
+) -> ReducedTys<'tcx> {
+ loop {
+ return match (from_ty.kind(), to_ty.kind()) {
+ (
+ ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. }),
+ ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. }),
+ ) => {
+ from_ty = from_sub_ty;
+ to_ty = to_sub_ty;
+ continue;
+ },
+ (ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }), _)
+ if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
+ {
+ ReducedTys::FromFatPtr { unsized_ty }
+ },
+ (_, ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }))
+ if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
+ {
+ ReducedTys::ToFatPtr { unsized_ty }
+ },
+ (ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. }), _) => {
+ ReducedTys::FromPtr { from_ty, to_ty }
+ },
+ (_, ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. })) => {
+ ReducedTys::ToPtr { from_ty, to_ty }
+ },
+ _ => ReducedTys::Other { from_ty, to_ty },
+ };
+ }
+}
+
+enum ReducedTy<'tcx> {
+ OrderedFields(Ty<'tcx>),
+ UnorderedFields(Ty<'tcx>),
+ Ref(Ty<'tcx>),
+ Other(Ty<'tcx>),
+ IntArray,
+}
+
+fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
+ loop {
+ ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
+ return match *ty.kind() {
+ ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray,
+ ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
+ ty = sub_ty;
+ continue;
+ },
+ ty::Tuple(args) => {
+ let mut iter = args.iter().map(GenericArg::expect_ty);
+ let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else {
+ return ReducedTy::OrderedFields(ty);
+ };
+ if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
+ ty = sized_ty;
+ continue;
+ }
+ ReducedTy::UnorderedFields(ty)
+ },
+ ty::Adt(def, substs) if def.is_struct() => {
+ if def.repr.inhibit_struct_field_reordering_opt() {
+ return ReducedTy::OrderedFields(ty);
+ }
+ let mut iter = def
+ .non_enum_variant()
+ .fields
+ .iter()
+ .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs));
+ let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else {
+ return ReducedTy::OrderedFields(ty);
+ };
+ if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
+ ty = sized_ty;
+ continue;
+ }
+ ReducedTy::UnorderedFields(ty)
+ },
+ ty::Ref(..) | ty::RawPtr(_) => ReducedTy::Ref(ty),
+ _ => ReducedTy::Other(ty),
+ };
+ }
+}
+
+fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty)
+ && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
+ {
+ layout.layout.size.bytes() == 0
+ } else {
+ false
+ }
+}
--- /dev/null
+#![warn(clippy::transmute_undefined_repr)]
+#![allow(clippy::unit_arg)]
+
+fn value<T>() -> T {
+ unimplemented!()
+}
+
+struct Empty;
+struct Ty<T>(T);
+struct Ty2<T, U>(T, U);
+
+#[repr(C)]
+struct Ty2C<T, U>(T, U);
+
+fn main() {
+ unsafe {
+ let _: () = core::mem::transmute(value::<Empty>());
+ let _: Empty = core::mem::transmute(value::<()>());
+
+ let _: Ty<u32> = core::mem::transmute(value::<u32>());
+ let _: Ty<u32> = core::mem::transmute(value::<u32>());
+
+ let _: Ty2C<u32, i32> = core::mem::transmute(value::<Ty2<u32, i32>>()); // Lint, Ty2 is unordered
+ let _: Ty2<u32, i32> = core::mem::transmute(value::<Ty2C<u32, i32>>()); // Lint, Ty2 is unordered
+
+ let _: Ty2<u32, i32> = core::mem::transmute(value::<Ty<Ty2<u32, i32>>>()); // Ok, Ty2 types are the same
+ let _: Ty<Ty2<u32, i32>> = core::mem::transmute(value::<Ty2<u32, i32>>()); // Ok, Ty2 types are the same
+
+ let _: Ty2<u32, f32> = core::mem::transmute(value::<Ty<Ty2<u32, i32>>>()); // Lint, different Ty2 instances
+ let _: Ty<Ty2<u32, i32>> = core::mem::transmute(value::<Ty2<u32, f32>>()); // Lint, different Ty2 instances
+
+ let _: Ty<&()> = core::mem::transmute(value::<&()>());
+ let _: &() = core::mem::transmute(value::<Ty<&()>>());
+
+ let _: &Ty2<u32, f32> = core::mem::transmute(value::<Ty<&Ty2<u32, i32>>>()); // Lint, different Ty2 instances
+ let _: Ty<&Ty2<u32, i32>> = core::mem::transmute(value::<&Ty2<u32, f32>>()); // Lint, different Ty2 instances
+
+ let _: Ty<usize> = core::mem::transmute(value::<&Ty2<u32, i32>>()); // Ok, pointer to usize conversion
+ let _: &Ty2<u32, i32> = core::mem::transmute(value::<Ty<usize>>()); // Ok, pointer to usize conversion
+
+ let _: Ty<[u8; 8]> = core::mem::transmute(value::<Ty2<u32, i32>>()); // Ok, transmute to byte array
+ let _: Ty2<u32, i32> = core::mem::transmute(value::<Ty<[u8; 8]>>()); // Ok, transmute from byte array
+ }
+}
--- /dev/null
+error: transmute from `Ty2<u32, i32>` which has an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:23:33
+ |
+LL | let _: Ty2C<u32, i32> = core::mem::transmute(value::<Ty2<u32, i32>>()); // Lint, Ty2 is unordered
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings`
+
+error: transmute into `Ty2<u32, i32>` which has an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:24:32
+ |
+LL | let _: Ty2<u32, i32> = core::mem::transmute(value::<Ty2C<u32, i32>>()); // Lint, Ty2 is unordered
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: transmute from `Ty<Ty2<u32, i32>>` to `Ty2<u32, f32>`, both of which have an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:29:32
+ |
+LL | let _: Ty2<u32, f32> = core::mem::transmute(value::<Ty<Ty2<u32, i32>>>()); // Lint, different Ty2 instances
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: two instances of the same generic type (`Ty2`) may have different layouts
+
+error: transmute from `Ty2<u32, f32>` to `Ty<Ty2<u32, i32>>`, both of which have an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:30:36
+ |
+LL | let _: Ty<Ty2<u32, i32>> = core::mem::transmute(value::<Ty2<u32, f32>>()); // Lint, different Ty2 instances
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: two instances of the same generic type (`Ty2`) may have different layouts
+
+error: transmute to `&Ty2<u32, f32>` which has an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:35:33
+ |
+LL | let _: &Ty2<u32, f32> = core::mem::transmute(value::<Ty<&Ty2<u32, i32>>>()); // Lint, different Ty2 instances
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: transmute from `&Ty2<u32, f32>` which has an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:36:37
+ |
+LL | let _: Ty<&Ty2<u32, i32>> = core::mem::transmute(value::<&Ty2<u32, f32>>()); // Lint, different Ty2 instances
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 6 previous errors
+