Skip to content

Commit 0c483f6

Browse files
committed
Auto merge of #8445 - asquared31415:slice_ptr_cast, r=llogiq
Llint for casting between raw slice pointers with different element sizes This lint disallows using `as` to convert from a raw pointer to a slice (e.g. `*const [i32]`, `*mut [Foo]`) to any other raw pointer to a slice if the element types have different sizes. When a raw slice pointer is cast, the data pointer and count metadata are preserved. This means that when the size of the inner slice's element type changes, the total number of bytes pointed to by the count changes. For example a `*const [i32]` with length 4 (four `i32` elements) is cast `as *const [u8]` the resulting pointer points to four `u8` elements at the same address, losing most of the data. When the size *increases* the resulting pointer will point to *more* data, and accessing that data will be UB. On its own, *producing* the pointer isn't actually a problem, but because any use of the pointer as a slice will either produce surprising behavior or cause UB I believe this is a correctness lint. If the pointer is not intended to be used as a slice, the user should instead use any of a number of methods to produce just a data pointer including an `as` cast to a thin pointer (e.g. `p as *const i32`) or if the pointer is being created from a slice, the `as_ptr` method on slices. Detecting the intended use of the pointer is outside the scope of this lint, but I believe this lint will also lead users to realize that a slice pointer is only for slices. There is an exception to this lint when either of the slice element types are zero sized (e.g `*mut [()]`). The total number of bytes pointed to by the slice with a zero sized element is zero. In that case preserving the length metadata is likely intended as a workaround to get the length metadata of a slice pointer though a zero sized slice. The lint does not forbid casting pointers to slices with the *same* element size as the cast was likely intended to reinterpret the data in the slice as some equivalently sized data and the resulting pointer will behave as intended. --- changelog: Added ``[`cast_slice_different_sizes`]``, a lint that disallows using `as`-casts to convert between raw pointers to slices when the elements have different sizes.
2 parents 48d5494 + f932c30 commit 0c483f6

12 files changed

+267
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,6 +3077,7 @@ Released 2018-09-13
30773077
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
30783078
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
30793079
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
3080+
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
30803081
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
30813082
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
30823083
[`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
@@ -78,6 +78,7 @@ store.register_lints(&[
7878
casts::CAST_PTR_ALIGNMENT,
7979
casts::CAST_REF_TO_MUT,
8080
casts::CAST_SIGN_LOSS,
81+
casts::CAST_SLICE_DIFFERENT_SIZES,
8182
casts::CHAR_LIT_AS_U8,
8283
casts::FN_TO_NUMERIC_CAST,
8384
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)