Add lint transumte_undefined_repr

This commit is contained in:
Jason Newcomb 2022-02-01 14:53:12 -05:00
parent 8ef87455b0
commit 3403b3e717
10 changed files with 415 additions and 1 deletions

View File

@ -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

View File

@ -277,6 +277,7 @@
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),

View File

@ -58,6 +58,7 @@
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),

View File

@ -473,6 +473,7 @@
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,

View File

@ -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)]

View File

@ -7,6 +7,7 @@
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 @@
"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,6 +394,7 @@
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
]);
impl<'tcx> LateLintPass<'tcx> for Transmute {
@ -402,7 +428,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
| 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);

View File

@ -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
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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