Skip to content

Commit f932c30

Browse files
committed
lint for casting raw pointers to slices with different element sizes
1 parent 53189ad commit f932c30

12 files changed

+267
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,7 @@ Released 2018-09-13
30763076
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
30773077
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
30783078
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
3079+
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
30793080
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
30803081
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
30813082
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt};
2+
use if_chain::if_chain;
3+
use rustc_ast::Mutability;
4+
use rustc_hir::{Expr, ExprKind, Node};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
7+
use rustc_semver::RustcVersion;
8+
9+
use super::CAST_SLICE_DIFFERENT_SIZES;
10+
11+
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
12+
let map = cx.tcx.hir();
13+
if_chain! {
14+
if let Some(parent_id) = map.find_parent_node(expr.hir_id);
15+
if let Some(parent) = map.find(parent_id);
16+
then {
17+
let expr = match parent {
18+
Node::Block(block) => {
19+
if let Some(parent_expr) = block.expr {
20+
parent_expr
21+
} else {
22+
return false;
23+
}
24+
},
25+
Node::Expr(expr) => expr,
26+
_ => return false,
27+
};
28+
29+
matches!(expr.kind, ExprKind::Cast(..))
30+
} else {
31+
false
32+
}
33+
}
34+
}
35+
36+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
37+
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
38+
if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
39+
return;
40+
}
41+
42+
// if this cast is the child of another cast expression then don't emit something for it, the full
43+
// chain will be analyzed
44+
if is_child_of_cast(cx, expr) {
45+
return;
46+
}
47+
48+
if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) {
49+
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) {
50+
let from_size = from_layout.size.bytes();
51+
let to_size = to_layout.size.bytes();
52+
if from_size != to_size && from_size != 0 && to_size != 0 {
53+
span_lint_and_then(
54+
cx,
55+
CAST_SLICE_DIFFERENT_SIZES,
56+
expr.span,
57+
&format!(
58+
"casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
59+
from_slice_ty, from_size, to_slice_ty, to_size,
60+
),
61+
|diag| {
62+
let cast_expr = match expr.kind {
63+
ExprKind::Cast(cast_expr, ..) => cast_expr,
64+
_ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"),
65+
};
66+
let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap();
67+
68+
let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl {
69+
Mutability::Mut => ("_mut", "mut"),
70+
Mutability::Not => ("", "const"),
71+
};
72+
let sugg = format!(
73+
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)"
74+
);
75+
76+
diag.span_suggestion(
77+
expr.span,
78+
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
79+
sugg,
80+
rustc_errors::Applicability::HasPlaceholders,
81+
);
82+
},
83+
);
84+
}
85+
}
86+
}
87+
}
88+
89+
/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
90+
/// the type is one of those slices
91+
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
92+
match ty.kind() {
93+
ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
94+
ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
95+
_ => None,
96+
},
97+
_ => None,
98+
}
99+
}
100+
101+
/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
102+
/// Returns None if the expr is not a Cast
103+
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
104+
if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
105+
let cast_to = cx.typeck_results().expr_ty(expr);
106+
let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
107+
if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
108+
Some((inner_from_ty, to_slice_ty))
109+
} else {
110+
let cast_from = cx.typeck_results().expr_ty(cast_expr);
111+
let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
112+
Some((from_slice_ty, to_slice_ty))
113+
}
114+
} else {
115+
None
116+
}
117+
}

clippy_lints/src/casts/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod cast_precision_loss;
55
mod cast_ptr_alignment;
66
mod cast_ref_to_mut;
77
mod cast_sign_loss;
8+
mod cast_slice_different_sizes;
89
mod char_lit_as_u8;
910
mod fn_to_numeric_cast;
1011
mod fn_to_numeric_cast_any;
@@ -409,6 +410,50 @@ declare_clippy_lint! {
409410
"casts from an enum type to an integral type which will truncate the value"
410411
}
411412

413+
declare_clippy_lint! {
414+
/// Checks for `as` casts between raw pointers to slices with differently sized elements.
415+
///
416+
/// ### Why is this bad?
417+
/// The produced raw pointer to a slice does not update its length metadata. The produced
418+
/// pointer will point to a different number of bytes than the original pointer because the
419+
/// length metadata of a raw slice pointer is in elements rather than bytes.
420+
/// Producing a slice reference from the raw pointer will either create a slice with
421+
/// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
422+
///
423+
/// ### Example
424+
/// // Missing data
425+
/// ```rust
426+
/// let a = [1_i32, 2, 3, 4];
427+
/// let p = &a as *const [i32] as *const [u8];
428+
/// unsafe {
429+
/// println!("{:?}", &*p);
430+
/// }
431+
/// ```
432+
/// // Undefined Behavior (note: also potential alignment issues)
433+
/// ```rust
434+
/// let a = [1_u8, 2, 3, 4];
435+
/// let p = &a as *const [u8] as *const [u32];
436+
/// unsafe {
437+
/// println!("{:?}", &*p);
438+
/// }
439+
/// ```
440+
/// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
441+
/// ```rust
442+
/// let a = [1_i32, 2, 3, 4];
443+
/// let old_ptr = &a as *const [i32];
444+
/// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
445+
/// // The length comes from the known length of 4 i32s times the 4 bytes per i32
446+
/// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
447+
/// unsafe {
448+
/// println!("{:?}", &*new_ptr);
449+
/// }
450+
/// ```
451+
#[clippy::version = "1.60.0"]
452+
pub CAST_SLICE_DIFFERENT_SIZES,
453+
correctness,
454+
"casting using `as` between raw pointers to slices of types with different sizes"
455+
}
456+
412457
pub struct Casts {
413458
msrv: Option<RustcVersion>,
414459
}
@@ -428,6 +473,7 @@ impl_lint_pass!(Casts => [
428473
CAST_LOSSLESS,
429474
CAST_REF_TO_MUT,
430475
CAST_PTR_ALIGNMENT,
476+
CAST_SLICE_DIFFERENT_SIZES,
431477
UNNECESSARY_CAST,
432478
FN_TO_NUMERIC_CAST_ANY,
433479
FN_TO_NUMERIC_CAST,
@@ -478,6 +524,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
478524
cast_ref_to_mut::check(cx, expr);
479525
cast_ptr_alignment::check(cx, expr);
480526
char_lit_as_u8::check(cx, expr);
527+
ptr_as_ptr::check(cx, expr, &self.msrv);
528+
cast_slice_different_sizes::check(cx, expr, &self.msrv);
481529
}
482530

483531
extract_msrv_attr!(LateContext);

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
2525
LintId::of(booleans::NONMINIMAL_BOOL),
2626
LintId::of(casts::CAST_ENUM_TRUNCATION),
2727
LintId::of(casts::CAST_REF_TO_MUT),
28+
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
2829
LintId::of(casts::CHAR_LIT_AS_U8),
2930
LintId::of(casts::FN_TO_NUMERIC_CAST),
3031
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),

clippy_lints/src/lib.register_correctness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
1313
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
1414
LintId::of(booleans::LOGIC_BUG),
1515
LintId::of(casts::CAST_REF_TO_MUT),
16+
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
1617
LintId::of(copies::IFS_SAME_COND),
1718
LintId::of(copies::IF_SAME_THEN_ELSE),
1819
LintId::of(derive::DERIVE_HASH_XOR_EQ),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ store.register_lints(&[
7777
casts::CAST_PTR_ALIGNMENT,
7878
casts::CAST_REF_TO_MUT,
7979
casts::CAST_SIGN_LOSS,
80+
casts::CAST_SLICE_DIFFERENT_SIZES,
8081
casts::CHAR_LIT_AS_U8,
8182
casts::FN_TO_NUMERIC_CAST,
8283
casts::FN_TO_NUMERIC_CAST_ANY,

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ msrv_aliases! {
2020
1,46,0 { CONST_IF_MATCH }
2121
1,45,0 { STR_STRIP_PREFIX }
2222
1,43,0 { LOG2_10, LOG10_2 }
23-
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
23+
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
2424
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
2525
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
2626
1,38,0 { POINTER_CAST }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
fn main() {
2+
let x: [i32; 3] = [1_i32, 2, 3];
3+
let r_x = &x;
4+
// Check casting through multiple bindings
5+
// Because it's separate, it does not check the cast back to something of the same size
6+
let a = r_x as *const [i32];
7+
let b = a as *const [u8];
8+
let c = b as *const [u32];
9+
10+
// loses data
11+
let loss = r_x as *const [i32] as *const [u8];
12+
13+
// Cast back to same size but different type loses no data, just type conversion
14+
// This is weird code but there's no reason for this lint specifically to fire *twice* on it
15+
let restore = r_x as *const [i32] as *const [u8] as *const [u32];
16+
17+
// Check casting through blocks is detected
18+
let loss_block_1 = { r_x as *const [i32] } as *const [u8];
19+
let loss_block_2 = {
20+
let _ = ();
21+
r_x as *const [i32]
22+
} as *const [u8];
23+
24+
// Check that resores of the same size are detected through blocks
25+
let restore_block_1 = { r_x as *const [i32] } as *const [u8] as *const [u32];
26+
let restore_block_2 = { ({ r_x as *const [i32] }) as *const [u8] } as *const [u32];
27+
let restore_block_3 = {
28+
let _ = ();
29+
({
30+
let _ = ();
31+
r_x as *const [i32]
32+
}) as *const [u8]
33+
} as *const [u32];
34+
35+
// Check that the result of a long chain of casts is detected
36+
let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
37+
let long_chain_restore =
38+
r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8] as *const [u32];
39+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
2+
--> $DIR/cast_slice_different_sizes.rs:7:13
3+
|
4+
LL | let b = a as *const [u8];
5+
| ^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(a as *const u8, ..)`
6+
|
7+
= note: `#[deny(clippy::cast_slice_different_sizes)]` on by default
8+
9+
error: casting between raw pointers to `[u8]` (element size 1) and `[u32]` (element size 4) does not adjust the count
10+
--> $DIR/cast_slice_different_sizes.rs:8:13
11+
|
12+
LL | let c = b as *const [u32];
13+
| ^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(b as *const u32, ..)`
14+
15+
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
16+
--> $DIR/cast_slice_different_sizes.rs:11:16
17+
|
18+
LL | let loss = r_x as *const [i32] as *const [u8];
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const u8, ..)`
20+
21+
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
22+
--> $DIR/cast_slice_different_sizes.rs:18:24
23+
|
24+
LL | let loss_block_1 = { r_x as *const [i32] } as *const [u8];
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts({ r_x as *const [i32] } as *const u8, ..)`
26+
27+
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
28+
--> $DIR/cast_slice_different_sizes.rs:19:24
29+
|
30+
LL | let loss_block_2 = {
31+
| ________________________^
32+
LL | | let _ = ();
33+
LL | | r_x as *const [i32]
34+
LL | | } as *const [u8];
35+
| |____________________^
36+
|
37+
help: replace with `ptr::slice_from_raw_parts`
38+
|
39+
LL ~ let loss_block_2 = core::ptr::slice_from_raw_parts({
40+
LL + let _ = ();
41+
LL + r_x as *const [i32]
42+
LL ~ } as *const u8, ..);
43+
|
44+
45+
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
46+
--> $DIR/cast_slice_different_sizes.rs:36:27
47+
|
48+
LL | let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const u8, ..)`
50+
51+
error: aborting due to 6 previous errors
52+

tests/ui/transmutes_expressible_as_ptr_casts.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ fn main() {
2525
let slice_ptr = &[0, 1, 2, 3] as *const [i32];
2626

2727
// ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
28-
let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u16] };
29-
let _ptr_to_unsized = slice_ptr as *const [u16];
28+
let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] };
29+
let _ptr_to_unsized = slice_ptr as *const [u32];
3030
// TODO: We could try testing vtable casts here too, but maybe
3131
// we should wait until std::raw::TraitObject is stabilized?
3232

tests/ui/transmutes_expressible_as_ptr_casts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ fn main() {
2525
let slice_ptr = &[0, 1, 2, 3] as *const [i32];
2626

2727
// ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
28-
let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
29-
let _ptr_to_unsized = slice_ptr as *const [u16];
28+
let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
29+
let _ptr_to_unsized = slice_ptr as *const [u32];
3030
// TODO: We could try testing vtable casts here too, but maybe
3131
// we should wait until std::raw::TraitObject is stabilized?
3232

tests/ui/transmutes_expressible_as_ptr_casts.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ LL | let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr
1717
error: transmute from a pointer to a pointer
1818
--> $DIR/transmutes_expressible_as_ptr_casts.rs:28:46
1919
|
20-
LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
21-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u16]`
20+
LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u32]`
2222

2323
error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead
2424
--> $DIR/transmutes_expressible_as_ptr_casts.rs:34:50

0 commit comments

Comments
 (0)