Skip to content

Commit 2ad02bf

Browse files
Add transmute_slice_to_larger_element_type lint
1 parent 5adeebf commit 2ad02bf

7 files changed

+125
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4794,6 +4794,7 @@ Released 2018-09-13
47944794
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
47954795
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
47964796
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
4797+
[`transmute_slice_to_larger_element_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_slice_to_larger_element_type
47974798
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
47984799
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
47994800
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null

clippy_dev/src/main.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ fn main() {
2121
);
2222
},
2323
Some(("fmt", matches)) => {
24-
fmt::run(matches.contains_id("check"), matches.contains_id("verbose"));
24+
let it = matches.contains_id("check");
25+
println!("{it}");
26+
fmt::run(it, matches.contains_id("verbose"));
2527
},
2628
Some(("update_lints", matches)) => {
2729
if matches.contains_id("print-only") {
@@ -134,7 +136,7 @@ fn get_clap_config() -> ArgMatches {
134136
.args([
135137
Arg::new("check")
136138
.long("check")
137-
.action(ArgAction::SetTrue)
139+
.required(false)
138140
.help("Use the rustfmt --check option"),
139141
Arg::new("verbose")
140142
.short('v')

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
577577
crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO,
578578
crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO,
579579
crate::transmute::TRANSMUTE_PTR_TO_REF_INFO,
580+
crate::transmute::TRANSMUTE_SLICE_TO_LARGER_ELEMENT_TYPE_INFO,
580581
crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO,
581582
crate::transmute::TRANSMUTING_NULL_INFO,
582583
crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO,

clippy_lints/src/transmute/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod transmute_num_to_bytes;
88
mod transmute_ptr_to_ptr;
99
mod transmute_ptr_to_ref;
1010
mod transmute_ref_to_ref;
11+
mod transmute_slice_to_larger_element_type;
1112
mod transmute_undefined_repr;
1213
mod transmutes_expressible_as_ptr_casts;
1314
mod transmuting_null;
@@ -438,6 +439,25 @@ declare_clippy_lint! {
438439
"transmute results in a null function pointer, which is undefined behavior"
439440
}
440441

442+
declare_clippy_lint! {
443+
/// ### What it does
444+
///
445+
/// ### Why is this bad?
446+
///
447+
/// ### Example
448+
/// ```rust
449+
/// // example code where clippy issues a warning
450+
/// ```
451+
/// Use instead:
452+
/// ```rust
453+
/// // example code which does not raise clippy warning
454+
/// ```
455+
#[clippy::version = "1.69.0"]
456+
pub TRANSMUTE_SLICE_TO_LARGER_ELEMENT_TYPE,
457+
correctness,
458+
"default lint description"
459+
}
460+
441461
pub struct Transmute {
442462
msrv: Msrv,
443463
}
@@ -458,6 +478,7 @@ impl_lint_pass!(Transmute => [
458478
TRANSMUTE_UNDEFINED_REPR,
459479
TRANSMUTING_NULL,
460480
TRANSMUTE_NULL_TO_FN,
481+
TRANSMUTE_SLICE_TO_LARGER_ELEMENT_TYPE,
461482
]);
462483
impl Transmute {
463484
#[must_use]
@@ -503,6 +524,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
503524
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
504525
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
505526
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
527+
| transmute_slice_to_larger_element_type::check(cx, e, from_ty, to_ty, arg)
506528
| (
507529
unsound_collection_transmute::check(cx, e, from_ty, to_ty)
508530
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use super::TRANSMUTE_SLICE_TO_LARGER_ELEMENT_TYPE;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::source::reindent_multiline;
4+
use clippy_utils::sugg;
5+
use clippy_utils::ty::approx_ty_size;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::Expr;
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty::{self, Ty};
10+
use std::borrow::Cow;
11+
12+
// TODO: Adjust the parameters as necessary
13+
pub(super) fn check<'tcx>(
14+
cx: &LateContext<'tcx>,
15+
call_to_transmute: &'tcx Expr<'_>,
16+
from_ty: Ty<'tcx>,
17+
to_ty: Ty<'tcx>,
18+
transmute_arg: &'tcx Expr<'_>,
19+
) -> bool {
20+
if let (ty::Ref(_, ty_from, _), ty::Ref(_, ty_to, _)) = (&from_ty.kind(), &to_ty.kind()) {
21+
if let (&ty::Slice(ty_elem_from), &ty::Slice(ty_elem_to)) = (&ty_from.kind(), &ty_to.kind()) {
22+
let ty_eleme_from_size = approx_ty_size(cx, *ty_elem_from);
23+
let ty_elem_to_size = approx_ty_size(cx, *ty_elem_to);
24+
if ty_eleme_from_size < ty_elem_to_size {
25+
// this is UB!!
26+
span_lint_and_then(
27+
cx,
28+
TRANSMUTE_SLICE_TO_LARGER_ELEMENT_TYPE,
29+
call_to_transmute.span,
30+
&format!("transmute from `&[{ty_elem_from}]` to `&[{ty_elem_to}]` results in undefined behavior"),
31+
|diag| {
32+
let transmute_arg = sugg::Sugg::hir(cx, transmute_arg, "..");
33+
// TODO: In this case, outer unsafe block is not needed anymore. It should be removed in
34+
// suggestion.
35+
let sugg_reallocate = format!(
36+
"{transmute_arg}\
37+
.iter()\
38+
.map(|item| unsafe {{ std::mem::transmute(item) }})\
39+
.collect::<Vec<_>>()\
40+
.to_slice()"
41+
);
42+
let sugg_reallocate = Cow::from(sugg_reallocate);
43+
let sugg_align_to = format!("std::slice::align_to::<{ty_elem_to}>({transmute_arg}).1");
44+
let sugg_align_to = Cow::from(sugg_align_to);
45+
diag.note("this transmute leads out-of-bounds read");
46+
diag.span_suggestions(
47+
call_to_transmute.span,
48+
"try",
49+
[
50+
reindent_multiline(sugg_reallocate, true, None).to_string(),
51+
// TODO: this suggestion does not check if there's prefix and postfix.
52+
// NOTE: this is not what user want to do if ty_elem_to is ZST; however,
53+
// this lint will not fire in such case anyway (ZSTs cannot be larger than any type).
54+
reindent_multiline(sugg_align_to, true, None).to_string(),
55+
],
56+
Applicability::Unspecified,
57+
);
58+
},
59+
);
60+
61+
true
62+
} else {
63+
false
64+
}
65+
} else {
66+
false
67+
}
68+
} else {
69+
false
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![allow(unused)]
2+
#![warn(clippy::transmute_slice_to_larger_element_type)]
3+
4+
fn i8_slice_to_i32_slice() {
5+
let i8_slice: &[i8] = &[1i8, 2, 3, 4];
6+
let i32_slice: &[i32] = unsafe { std::mem::transmute(i8_slice) };
7+
}
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: transmute from `&[i8]` to `&[i32]` results in undefined behavior
2+
--> $DIR/transmute_slice_to_larger_element_type.rs:6:38
3+
|
4+
LL | let i32_slice: &[i32] = unsafe { std::mem::transmute(i8_slice) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: this transmute leads out-of-bounds read
8+
= note: `-D clippy::transmute-slice-to-larger-element-type` implied by `-D warnings`
9+
help: try
10+
|
11+
LL | let i32_slice: &[i32] = unsafe { i8_slice.iter().map(|item| unsafe { std::mem::transmute(item) }).collect::<Vec<_>>().to_slice() };
12+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
LL | let i32_slice: &[i32] = unsafe { std::slice::align_to::<i32>(i8_slice).1 };
14+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15+
16+
error: aborting due to previous error
17+

0 commit comments

Comments
 (0)