Skip to content

Commit 3403b3e

Browse files
committed
Add lint transumte_undefined_repr
1 parent 8ef8745 commit 3403b3e

File tree

10 files changed

+415
-1
lines changed

10 files changed

+415
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,6 +3304,7 @@ Released 2018-09-13
33043304
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
33053305
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
33063306
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
3307+
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
33073308
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
33083309
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
33093310
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
277277
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
278278
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
279279
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
280+
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
280281
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
281282
LintId::of(transmute::WRONG_TRANSMUTE),
282283
LintId::of(transmuting_null::TRANSMUTING_NULL),

clippy_lints/src/lib.register_correctness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
5858
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
5959
LintId::of(swap::ALMOST_SWAPPED),
6060
LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
61+
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
6162
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
6263
LintId::of(transmute::WRONG_TRANSMUTE),
6364
LintId::of(transmuting_null::TRANSMUTING_NULL),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ store.register_lints(&[
473473
transmute::TRANSMUTE_NUM_TO_BYTES,
474474
transmute::TRANSMUTE_PTR_TO_PTR,
475475
transmute::TRANSMUTE_PTR_TO_REF,
476+
transmute::TRANSMUTE_UNDEFINED_REPR,
476477
transmute::UNSOUND_COLLECTION_TRANSMUTE,
477478
transmute::USELESS_TRANSMUTE,
478479
transmute::WRONG_TRANSMUTE,

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![feature(control_flow_enum)]
66
#![feature(drain_filter)]
77
#![feature(iter_intersperse)]
8+
#![feature(let_chains)]
89
#![feature(let_else)]
910
#![feature(once_cell)]
1011
#![feature(rustc_private)]

clippy_lints/src/transmute/mod.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod transmute_num_to_bytes;
77
mod transmute_ptr_to_ptr;
88
mod transmute_ptr_to_ref;
99
mod transmute_ref_to_ref;
10+
mod transmute_undefined_repr;
1011
mod transmutes_expressible_as_ptr_casts;
1112
mod unsound_collection_transmute;
1213
mod useless_transmute;
@@ -355,6 +356,30 @@ declare_clippy_lint! {
355356
"transmute between collections of layout-incompatible types"
356357
}
357358

359+
declare_clippy_lint! {
360+
/// ### What it does
361+
/// Checks for transmutes either to or from a type which does not have a defined representation.
362+
///
363+
/// ### Why is this bad?
364+
/// The results of such a transmute are not defined.
365+
///
366+
/// ### Example
367+
/// ```rust
368+
/// struct Foo<T>(u32, T);
369+
/// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
370+
/// ```
371+
/// Use instead:
372+
/// ```rust
373+
/// #[repr(C)]
374+
/// struct Foo<T>(u32, T);
375+
/// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
376+
/// ```
377+
#[clippy::version = "1.60.0"]
378+
pub TRANSMUTE_UNDEFINED_REPR,
379+
correctness,
380+
"transmute to or from a type with an undefined representation"
381+
}
382+
358383
declare_lint_pass!(Transmute => [
359384
CROSSPOINTER_TRANSMUTE,
360385
TRANSMUTE_PTR_TO_REF,
@@ -369,6 +394,7 @@ declare_lint_pass!(Transmute => [
369394
TRANSMUTE_NUM_TO_BYTES,
370395
UNSOUND_COLLECTION_TRANSMUTE,
371396
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
397+
TRANSMUTE_UNDEFINED_REPR,
372398
]);
373399

374400
impl<'tcx> LateLintPass<'tcx> for Transmute {
@@ -402,7 +428,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
402428
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
403429
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
404430
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
405-
| unsound_collection_transmute::check(cx, e, from_ty, to_ty);
431+
| (
432+
unsound_collection_transmute::check(cx, e, from_ty, to_ty)
433+
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty)
434+
);
406435

407436
if !linted {
408437
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
use super::TRANSMUTE_UNDEFINED_REPR;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use rustc_hir::Expr;
4+
use rustc_lint::LateContext;
5+
use rustc_middle::ty::subst::{GenericArg, Subst};
6+
use rustc_middle::ty::{self, Ty, TyS, TypeAndMut};
7+
use rustc_span::Span;
8+
9+
#[allow(clippy::too_many_lines)]
10+
pub(super) fn check<'tcx>(
11+
cx: &LateContext<'tcx>,
12+
e: &'tcx Expr<'_>,
13+
from_ty_orig: Ty<'tcx>,
14+
to_ty_orig: Ty<'tcx>,
15+
) -> bool {
16+
let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
17+
let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
18+
19+
while !TyS::same_type(from_ty, to_ty) {
20+
match reduce_refs(cx, e.span, from_ty, to_ty) {
21+
ReducedTys::FromFatPtr { unsized_ty, .. } => {
22+
span_lint_and_then(
23+
cx,
24+
TRANSMUTE_UNDEFINED_REPR,
25+
e.span,
26+
&format!("transmute from `{}` which has an undefined layout", from_ty_orig),
27+
|diag| {
28+
if !TyS::same_type(from_ty_orig.peel_refs(), unsized_ty) {
29+
diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
30+
}
31+
},
32+
);
33+
return true;
34+
},
35+
ReducedTys::ToFatPtr { unsized_ty, .. } => {
36+
span_lint_and_then(
37+
cx,
38+
TRANSMUTE_UNDEFINED_REPR,
39+
e.span,
40+
&format!("transmute to `{}` which has an undefined layout", to_ty_orig),
41+
|diag| {
42+
if !TyS::same_type(to_ty_orig.peel_refs(), unsized_ty) {
43+
diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
44+
}
45+
},
46+
);
47+
return true;
48+
},
49+
ReducedTys::ToPtr {
50+
from_ty: from_sub_ty,
51+
to_ty: to_sub_ty,
52+
} => match reduce_ty(cx, from_sub_ty) {
53+
ReducedTy::UnorderedFields(from_ty) => {
54+
span_lint_and_then(
55+
cx,
56+
TRANSMUTE_UNDEFINED_REPR,
57+
e.span,
58+
&format!("transmute from `{}` which has an undefined layout", from_ty_orig),
59+
|diag| {
60+
if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
61+
diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
62+
}
63+
},
64+
);
65+
return true;
66+
},
67+
ReducedTy::Ref(from_sub_ty) => {
68+
from_ty = from_sub_ty;
69+
to_ty = to_sub_ty;
70+
continue;
71+
},
72+
_ => break,
73+
},
74+
ReducedTys::FromPtr {
75+
from_ty: from_sub_ty,
76+
to_ty: to_sub_ty,
77+
} => match reduce_ty(cx, to_sub_ty) {
78+
ReducedTy::UnorderedFields(to_ty) => {
79+
span_lint_and_then(
80+
cx,
81+
TRANSMUTE_UNDEFINED_REPR,
82+
e.span,
83+
&format!("transmute to `{}` which has an undefined layout", to_ty_orig),
84+
|diag| {
85+
if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
86+
diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
87+
}
88+
},
89+
);
90+
return true;
91+
},
92+
ReducedTy::Ref(to_sub_ty) => {
93+
from_ty = from_sub_ty;
94+
to_ty = to_sub_ty;
95+
continue;
96+
},
97+
_ => break,
98+
},
99+
ReducedTys::Other {
100+
from_ty: from_sub_ty,
101+
to_ty: to_sub_ty,
102+
} => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
103+
(ReducedTy::IntArray, _) | (_, ReducedTy::IntArray) => return false,
104+
(ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty))
105+
if !TyS::same_type(from_ty, to_ty) =>
106+
{
107+
span_lint_and_then(
108+
cx,
109+
TRANSMUTE_UNDEFINED_REPR,
110+
e.span,
111+
&format!(
112+
"transmute from `{}` to `{}`, both of which have an undefined layout",
113+
from_ty_orig, to_ty_orig
114+
),
115+
|diag| {
116+
if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def())
117+
&& from_def == to_def
118+
{
119+
diag.note(&format!(
120+
"two instances of the same generic type (`{}`) may have different layouts",
121+
cx.tcx.item_name(from_def.did)
122+
));
123+
} else {
124+
if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
125+
diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
126+
}
127+
if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
128+
diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
129+
}
130+
}
131+
},
132+
);
133+
return true;
134+
},
135+
(
136+
ReducedTy::UnorderedFields(from_ty),
137+
ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
138+
) => {
139+
span_lint_and_then(
140+
cx,
141+
TRANSMUTE_UNDEFINED_REPR,
142+
e.span,
143+
&format!("transmute from `{}` which has an undefined layout", from_ty_orig),
144+
|diag| {
145+
if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) {
146+
diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
147+
}
148+
},
149+
);
150+
return true;
151+
},
152+
(
153+
ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
154+
ReducedTy::UnorderedFields(to_ty),
155+
) => {
156+
span_lint_and_then(
157+
cx,
158+
TRANSMUTE_UNDEFINED_REPR,
159+
e.span,
160+
&format!("transmute into `{}` which has an undefined layout", to_ty_orig),
161+
|diag| {
162+
if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) {
163+
diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
164+
}
165+
},
166+
);
167+
return true;
168+
},
169+
(ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => {
170+
from_ty = from_sub_ty;
171+
to_ty = to_sub_ty;
172+
continue;
173+
},
174+
(
175+
ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
176+
ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
177+
)
178+
| (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break,
179+
},
180+
}
181+
}
182+
183+
false
184+
}
185+
186+
enum ReducedTys<'tcx> {
187+
FromFatPtr { unsized_ty: Ty<'tcx> },
188+
ToFatPtr { unsized_ty: Ty<'tcx> },
189+
ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
190+
FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
191+
Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
192+
}
193+
194+
fn reduce_refs<'tcx>(
195+
cx: &LateContext<'tcx>,
196+
span: Span,
197+
mut from_ty: Ty<'tcx>,
198+
mut to_ty: Ty<'tcx>,
199+
) -> ReducedTys<'tcx> {
200+
loop {
201+
return match (from_ty.kind(), to_ty.kind()) {
202+
(
203+
ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. }),
204+
ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. }),
205+
) => {
206+
from_ty = from_sub_ty;
207+
to_ty = to_sub_ty;
208+
continue;
209+
},
210+
(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }), _)
211+
if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
212+
{
213+
ReducedTys::FromFatPtr { unsized_ty }
214+
},
215+
(_, ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }))
216+
if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
217+
{
218+
ReducedTys::ToFatPtr { unsized_ty }
219+
},
220+
(ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. }), _) => {
221+
ReducedTys::FromPtr { from_ty, to_ty }
222+
},
223+
(_, ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. })) => {
224+
ReducedTys::ToPtr { from_ty, to_ty }
225+
},
226+
_ => ReducedTys::Other { from_ty, to_ty },
227+
};
228+
}
229+
}
230+
231+
enum ReducedTy<'tcx> {
232+
OrderedFields(Ty<'tcx>),
233+
UnorderedFields(Ty<'tcx>),
234+
Ref(Ty<'tcx>),
235+
Other(Ty<'tcx>),
236+
IntArray,
237+
}
238+
239+
fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
240+
loop {
241+
ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
242+
return match *ty.kind() {
243+
ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray,
244+
ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
245+
ty = sub_ty;
246+
continue;
247+
},
248+
ty::Tuple(args) => {
249+
let mut iter = args.iter().map(GenericArg::expect_ty);
250+
let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else {
251+
return ReducedTy::OrderedFields(ty);
252+
};
253+
if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
254+
ty = sized_ty;
255+
continue;
256+
}
257+
ReducedTy::UnorderedFields(ty)
258+
},
259+
ty::Adt(def, substs) if def.is_struct() => {
260+
if def.repr.inhibit_struct_field_reordering_opt() {
261+
return ReducedTy::OrderedFields(ty);
262+
}
263+
let mut iter = def
264+
.non_enum_variant()
265+
.fields
266+
.iter()
267+
.map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs));
268+
let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else {
269+
return ReducedTy::OrderedFields(ty);
270+
};
271+
if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
272+
ty = sized_ty;
273+
continue;
274+
}
275+
ReducedTy::UnorderedFields(ty)
276+
},
277+
ty::Ref(..) | ty::RawPtr(_) => ReducedTy::Ref(ty),
278+
_ => ReducedTy::Other(ty),
279+
};
280+
}
281+
}
282+
283+
fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
284+
if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty)
285+
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
286+
{
287+
layout.layout.size.bytes() == 0
288+
} else {
289+
false
290+
}
291+
}

tests/ui/crashes/ice-4968.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Test for https://github.com/rust-lang/rust-clippy/issues/4968
44

55
#![warn(clippy::unsound_collection_transmute)]
6+
#![allow(clippy::transmute_undefined_repr)]
67

78
trait Trait {
89
type Assoc;

0 commit comments

Comments
 (0)