Skip to content

Commit e246285

Browse files
committed
lint for casting raw pointers to slices with different element sizes
1 parent 4931cab commit e246285

12 files changed

+279
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3075,6 +3075,7 @@ Released 2018-09-13
30753075
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
30763076
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
30773077
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
3078+
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
30783079
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
30793080
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
30803081
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 {}) to `[{}]` (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+
diag.help("the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes");
84+
},
85+
);
86+
}
87+
}
88+
}
89+
}
90+
91+
/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
92+
/// the type is one of those slices
93+
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
94+
match ty.kind() {
95+
ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
96+
ty::Slice(ty) => Some(TypeAndMut { ty, mutbl: *mutbl }),
97+
_ => None,
98+
},
99+
_ => None,
100+
}
101+
}
102+
103+
/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
104+
/// Returns None if the expr is not a Cast
105+
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
106+
if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
107+
let cast_to = cx.typeck_results().expr_ty(expr);
108+
let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
109+
if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
110+
Some((inner_from_ty, to_slice_ty))
111+
} else {
112+
let cast_from = cx.typeck_results().expr_ty(cast_expr);
113+
let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
114+
Some((from_slice_ty, to_slice_ty))
115+
}
116+
} else {
117+
None
118+
}
119+
}

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;
@@ -390,6 +391,51 @@ declare_clippy_lint! {
390391
"casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
391392
}
392393

394+
declare_clippy_lint! {
395+
/// ### What it does
396+
/// Checks for `as` casts between raw pointers to slices with elements with different sizes.
397+
///
398+
/// ### Why is this bad?
399+
/// The produced raw pointer to a slice does not update its length metadata. The produced
400+
/// pointer will point to a different number of bytes than the original pointer because the
401+
/// length metadata of a raw slice pointer is in elements rather than bytes.
402+
/// Producing a slice reference from the raw pointer will either create a slice with
403+
/// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
404+
///
405+
/// ### Example
406+
/// // Missing data
407+
/// ```rust
408+
/// let a = [1_i32, 2, 3, 4];
409+
/// let p = &a as *const [i32] as *const [u8];
410+
/// unsafe {
411+
/// println!("{:?}", &*p);
412+
/// }
413+
/// ```
414+
/// // Undefined Behavior (note: also potential alignment issues)
415+
/// ```rust
416+
/// let a = [1_u8, 2, 3, 4];
417+
/// let p = &a as *const [u8] as *const [u32];
418+
/// unsafe {
419+
/// println!("{:?}", &*p);
420+
/// }
421+
/// ```
422+
/// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
423+
/// ```rust
424+
/// let a = [1_i32, 2, 3, 4];
425+
/// let old_ptr = &a as *const [i32];
426+
/// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
427+
/// // The length comes from the known length of 4 i32s times the 4 bytes per i32
428+
/// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
429+
/// unsafe {
430+
/// println!("{:?}", &*new_ptr);
431+
/// }
432+
/// ```
433+
#[clippy::version = "1.60.0"]
434+
pub CAST_SLICE_DIFFERENT_SIZES,
435+
correctness,
436+
"casting using `as` between raw pointers to slices of types with different sizes"
437+
}
438+
393439
pub struct Casts {
394440
msrv: Option<RustcVersion>,
395441
}
@@ -409,6 +455,7 @@ impl_lint_pass!(Casts => [
409455
CAST_LOSSLESS,
410456
CAST_REF_TO_MUT,
411457
CAST_PTR_ALIGNMENT,
458+
CAST_SLICE_DIFFERENT_SIZES,
412459
UNNECESSARY_CAST,
413460
FN_TO_NUMERIC_CAST_ANY,
414461
FN_TO_NUMERIC_CAST,
@@ -456,6 +503,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
456503
cast_ptr_alignment::check(cx, expr);
457504
char_lit_as_u8::check(cx, expr);
458505
ptr_as_ptr::check(cx, expr, &self.msrv);
506+
cast_slice_different_sizes::check(cx, expr, &self.msrv);
459507
}
460508

461509
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
@@ -22,6 +22,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
2222
LintId::of(booleans::LOGIC_BUG),
2323
LintId::of(booleans::NONMINIMAL_BOOL),
2424
LintId::of(casts::CAST_REF_TO_MUT),
25+
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
2526
LintId::of(casts::CHAR_LIT_AS_U8),
2627
LintId::of(casts::FN_TO_NUMERIC_CAST),
2728
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
@@ -74,6 +74,7 @@ store.register_lints(&[
7474
casts::CAST_PTR_ALIGNMENT,
7575
casts::CAST_REF_TO_MUT,
7676
casts::CAST_SIGN_LOSS,
77+
casts::CAST_SLICE_DIFFERENT_SIZES,
7778
casts::CHAR_LIT_AS_U8,
7879
casts::FN_TO_NUMERIC_CAST,
7980
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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
error: casting between raw pointers to `[i32]` (element size 4) to `[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+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
9+
10+
error: casting between raw pointers to `[u8]` (element size 1) to `[u32]` (element size 4) does not adjust the count
11+
--> $DIR/cast_slice_different_sizes.rs:8:13
12+
|
13+
LL | let c = b as *const [u32];
14+
| ^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(b as *const u32, ..)`
15+
|
16+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
17+
18+
error: casting between raw pointers to `[i32]` (element size 4) to `[u8]` (element size 1) does not adjust the count
19+
--> $DIR/cast_slice_different_sizes.rs:11:16
20+
|
21+
LL | let loss = r_x as *const [i32] as *const [u8];
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const u8, ..)`
23+
|
24+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
25+
26+
error: casting between raw pointers to `[i32]` (element size 4) to `[u8]` (element size 1) does not adjust the count
27+
--> $DIR/cast_slice_different_sizes.rs:18:24
28+
|
29+
LL | let loss_block_1 = { r_x as *const [i32] } as *const [u8];
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts({ r_x as *const [i32] } as *const u8, ..)`
31+
|
32+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
33+
34+
error: casting between raw pointers to `[i32]` (element size 4) to `[u8]` (element size 1) does not adjust the count
35+
--> $DIR/cast_slice_different_sizes.rs:19:24
36+
|
37+
LL | let loss_block_2 = {
38+
| ________________________^
39+
LL | | let _ = ();
40+
LL | | r_x as *const [i32]
41+
LL | | } as *const [u8];
42+
| |____________________^
43+
|
44+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
45+
help: replace with `ptr::slice_from_raw_parts`
46+
|
47+
LL ~ let loss_block_2 = core::ptr::slice_from_raw_parts({
48+
LL + let _ = ();
49+
LL + r_x as *const [i32]
50+
LL ~ } as *const u8, ..);
51+
|
52+
53+
error: casting between raw pointers to `[i32]` (element size 4) to `[u8]` (element size 1) does not adjust the count
54+
--> $DIR/cast_slice_different_sizes.rs:36:27
55+
|
56+
LL | let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
57+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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, ..)`
58+
|
59+
= help: the count in elements of raw pointers to slices is preserved across `as`-casts so the slice covers a different number of bytes
60+
61+
error: aborting due to 6 previous errors
62+

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)