Skip to content

Add lint transmute_undefined_repr #8398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 6, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3304,6 +3304,7 @@ Released 2018-09-13
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
@@ -277,6 +277,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_correctness.rs
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(swap::ALMOST_SWAPPED),
LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
@@ -473,6 +473,7 @@ store.register_lints(&[
transmute::TRANSMUTE_NUM_TO_BYTES,
transmute::TRANSMUTE_PTR_TO_PTR,
transmute::TRANSMUTE_PTR_TO_REF,
transmute::TRANSMUTE_UNDEFINED_REPR,
transmute::UNSOUND_COLLECTION_TRANSMUTE,
transmute::USELESS_TRANSMUTE,
transmute::WRONG_TRANSMUTE,
1 change: 1 addition & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#![feature(control_flow_enum)]
#![feature(drain_filter)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
#![feature(let_else)]
#![feature(once_cell)]
#![feature(rustc_private)]
60 changes: 44 additions & 16 deletions clippy_lints/src/transmute/mod.rs
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ mod transmute_num_to_bytes;
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;
@@ -355,6 +356,30 @@ declare_clippy_lint! {
"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,
@@ -369,13 +394,13 @@ declare_lint_pass!(Transmute => [
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
]);

impl<'tcx> LateLintPass<'tcx> for Transmute {
#[allow(clippy::similar_names, clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(path_expr, args) = e.kind;
if let ExprKind::Call(path_expr, [arg]) = e.kind;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if let ExprKind::Path(ref qpath) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
@@ -385,28 +410,31 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
let const_context = in_constant(cx, e.hir_id);

let from_ty = cx.typeck_results().expr_ty(&args[0]);
let from_ty = cx.typeck_results().expr_ty(arg);
let to_ty = cx.typeck_results().expr_ty(e);

// If useless_transmute is triggered, the other lints can be skipped.
if useless_transmute::check(cx, e, from_ty, to_ty, args) {
if useless_transmute::check(cx, e, from_ty, to_ty, arg) {
return;
}

let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty);
linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty);
linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath);
linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args);
linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context);
linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args);
linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
| 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)
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty)
);

if !linted {
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args);
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
}
}
}
29 changes: 14 additions & 15 deletions clippy_lints/src/transmute/transmute_float_to_int.rs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
mut arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
@@ -26,37 +26,36 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|diag| {
let mut expr = &args[0];
let mut arg = sugg::Sugg::hir(cx, expr, "..");
let mut sugg = sugg::Sugg::hir(cx, arg, "..");

if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
expr = inner_expr;
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
arg = inner_expr;
}

if_chain! {
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
if let ExprKind::Lit(lit) = &expr.kind;
if let ExprKind::Lit(lit) = &arg.kind;
if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node;
then {
let op = format!("{}{}", arg, float_ty.name_str()).into();
match arg {
sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op),
_ => arg = sugg::Sugg::NonParen(op)
let op = format!("{}{}", sugg, float_ty.name_str()).into();
match sugg {
sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
_ => sugg = sugg::Sugg::NonParen(op)
}
}
}

arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into());
sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_par()).into());

// cast the result of `to_bits` if `to_ty` is signed
arg = if let ty::Int(int_ty) = to_ty.kind() {
arg.as_ty(int_ty.name_str().to_string())
sugg = if let ty::Int(int_ty) = to_ty.kind() {
sugg.as_ty(int_ty.name_str().to_string())
} else {
arg
sugg
};

diag.span_suggestion(e.span, "consider using", arg.to_string(), Applicability::Unspecified);
diag.span_suggestion(e.span, "consider using", sugg.to_string(), Applicability::Unspecified);
},
);
true
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_int_to_bool.rs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => {
@@ -25,7 +25,7 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{}` to a `bool`", from_ty),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let arg = sugg::Sugg::hir(cx, arg, "..");
let zero = sugg::Sugg::NonParen(Cow::from("0"));
diag.span_suggestion(
e.span,
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_int_to_char.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
@@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{}` to a `char`", from_ty),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(_) = from_ty.kind() {
arg.as_ty(ast::UintTy::U32.name_str())
} else {
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_int_to_float.rs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
@@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(int_ty) = from_ty.kind() {
arg.as_ty(format!(
"u{}",
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_num_to_bytes.rs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
@@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
e.span,
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let arg = sugg::Sugg::hir(cx, arg, "..");
diag.span_suggestion(
e.span,
"consider using `to_ne_bytes()`",
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_ptr_to_ptr.rs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::RawPtr(_), ty::RawPtr(to_ty)) => {
@@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
e.span,
"transmute from a pointer to a pointer",
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty));
diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified);
}
4 changes: 2 additions & 2 deletions clippy_lints/src/transmute/transmute_ptr_to_ref.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
qpath: &'tcx QPath<'_>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
@@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
from_ty, to_ty
),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let arg = sugg::Sugg::hir(cx, arg, "..");
let (deref, cast) = if *mutbl == Mutability::Mut {
("&mut *", "*mut")
} else {
6 changes: 3 additions & 3 deletions clippy_lints/src/transmute/transmute_ref_to_ref.rs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
let mut triggered = false;
@@ -41,7 +41,7 @@ pub(super) fn check<'tcx>(
format!(
"std::str::from_utf8{}({}).unwrap()",
postfix,
snippet(cx, args[0].span, ".."),
snippet(cx, arg.span, ".."),
),
Applicability::Unspecified,
);
@@ -54,7 +54,7 @@ pub(super) fn check<'tcx>(
TRANSMUTE_PTR_TO_PTR,
e.span,
"transmute from a reference to a reference",
|diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
let ty_from_and_mut = ty::TypeAndMut {
ty: ty_from,
mutbl: *from_mutbl
291 changes: 291 additions & 0 deletions clippy_lints/src/transmute/transmute_undefined_repr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
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
}
}
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
) -> bool {
if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
span_lint_and_then(
@@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(
from_ty, to_ty
),
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
let sugg = arg.as_ty(&to_ty.to_string()).to_string();
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
}
6 changes: 3 additions & 3 deletions clippy_lints/src/transmute/useless_transmute.rs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
arg: &'tcx Expr<'_>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
_ if from_ty == to_ty => {
@@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
e.span,
"transmute from a reference to a pointer",
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
let rty_and_mut = ty::TypeAndMut {
ty: rty,
mutbl: *rty_mutbl,
@@ -57,7 +57,7 @@ pub(super) fn check<'tcx>(
e.span,
"transmute from an integer to a pointer",
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
diag.span_suggestion(
e.span,
"try",
1 change: 1 addition & 0 deletions tests/ui/crashes/ice-4968.rs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
// Test for https://github.com/rust-lang/rust-clippy/issues/4968

#![warn(clippy::unsound_collection_transmute)]
#![allow(clippy::transmute_undefined_repr)]

trait Trait {
type Assoc;
44 changes: 44 additions & 0 deletions tests/ui/transmute_undefined_repr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![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
}
}
44 changes: 44 additions & 0 deletions tests/ui/transmute_undefined_repr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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