Skip to content

Commit 014b80c

Browse files
committed
feat: add cast_ptr_sized_int lint
Introduce a new restriction lint to identify casts between pointer-sized integer types (`usize`, `isize`) and fixed-size integer types. Encourage the use of `TryFrom` or `TryInto` over direct `as` casts to make potential platform-specific truncation or zero-extension explicit and handled. Also Provide machine-applicable suggestions to replace risky casts with fallible conversions while excluding constant contexts where such traits are not yet stable. Closes: #9231
1 parent 34fab5c commit 014b80c

File tree

9 files changed

+859
-0
lines changed

9 files changed

+859
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6318,6 +6318,7 @@ Released 2018-09-13
63186318
[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
63196319
[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
63206320
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
6321+
[`cast_ptr_sized_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_sized_int
63216322
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
63226323
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
63236324
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_in_const_context;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::is_isize_or_usize;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::Expr;
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty::Ty;
10+
use rustc_span::Span;
11+
12+
use super::CAST_PTR_SIZED_INT;
13+
14+
/// Checks for casts between pointer-sized integer types (`usize`/`isize`) and
15+
/// fixed-size integer types (like `u64`, `i32`, etc.).
16+
pub(super) fn check<'tcx>(
17+
cx: &LateContext<'tcx>,
18+
expr: &Expr<'_>,
19+
cast_from_expr: &Expr<'_>,
20+
cast_from: Ty<'tcx>,
21+
cast_to: Ty<'tcx>,
22+
cast_to_span: Span,
23+
) {
24+
// Only lint integer-to-integer casts
25+
if !cast_from.is_integral() || !cast_to.is_integral() {
26+
return;
27+
}
28+
29+
let from_is_ptr_sized = is_isize_or_usize(cast_from);
30+
let to_is_ptr_sized = is_isize_or_usize(cast_to);
31+
32+
// Only lint if exactly one side is pointer-sized
33+
// (if both are pointer-sized or neither is, other lints handle it)
34+
if from_is_ptr_sized == to_is_ptr_sized {
35+
return;
36+
}
37+
38+
let (ptr_sized_ty, fixed_ty, direction) = if from_is_ptr_sized {
39+
(cast_from, cast_to, "to")
40+
} else {
41+
(cast_to, cast_from, "from")
42+
};
43+
44+
let msg = format!("casting `{cast_from}` to `{cast_to}`: the result depends on the target architecture");
45+
46+
span_lint_and_then(cx, CAST_PTR_SIZED_INT, expr.span, msg, |diag| {
47+
let help_msg = format!(
48+
"`{ptr_sized_ty}` varies in size depending on the target architecture (32-bit or 64-bit), \
49+
so casting {direction} `{fixed_ty}` may behave differently across platforms"
50+
);
51+
diag.help(help_msg);
52+
53+
// Suggest using TryFrom/TryInto for safer conversion
54+
if !is_in_const_context(cx) {
55+
let cast_to_snip = snippet(cx, cast_to_span, "..");
56+
let suggestion = if cast_to_snip == "_" {
57+
format!("{}.try_into()", Sugg::hir(cx, cast_from_expr, "..").maybe_paren())
58+
} else {
59+
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_from_expr, ".."))
60+
};
61+
62+
diag.span_suggestion_verbose(
63+
expr.span,
64+
"if this is intentional, consider using `TryFrom` or `TryInto` and handle the error",
65+
suggestion,
66+
Applicability::MaybeIncorrect,
67+
);
68+
}
69+
});
70+
}

clippy_lints/src/casts/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod cast_possible_truncation;
1010
mod cast_possible_wrap;
1111
mod cast_precision_loss;
1212
mod cast_ptr_alignment;
13+
mod cast_ptr_sized_int;
1314
mod cast_sign_loss;
1415
mod cast_slice_different_sizes;
1516
mod cast_slice_from_raw_parts;
@@ -814,6 +815,42 @@ declare_clippy_lint! {
814815
"casting a primitive method pointer to any integer type"
815816
}
816817

818+
declare_clippy_lint! {
819+
/// ### What it does
820+
/// Checks for casts between pointer-sized integer types (`usize`/`isize`)
821+
/// and fixed-size integer types (like `u64`, `i32`, etc.).
822+
///
823+
/// ### Why is this bad?
824+
/// `usize` and `isize` have sizes that depend on the target architecture
825+
/// (32-bit on 32-bit platforms, 64-bit on 64-bit platforms). Casting between
826+
/// these and fixed-size integers can lead to subtle, platform-specific bugs:
827+
///
828+
/// - `usize as u64`: On 64-bit platforms this is lossless, but on 32-bit
829+
/// platforms the upper 32 bits are always zero.
830+
/// - `u64 as usize`: On 32-bit platforms this truncates, but on 64-bit
831+
/// platforms it's lossless.
832+
///
833+
/// Using `TryFrom`/`TryInto` makes the potential for failure explicit.
834+
///
835+
/// ### Example
836+
/// ```no_run
837+
/// pub fn foo(x: usize) -> u64 {
838+
/// x as u64
839+
/// }
840+
/// ```
841+
///
842+
/// Use instead:
843+
/// ```no_run
844+
/// pub fn foo(x: usize) -> u64 {
845+
/// u64::try_from(x).expect("usize should fit in u64")
846+
/// }
847+
/// ```
848+
#[clippy::version = "1.89.0"]
849+
pub CAST_PTR_SIZED_INT,
850+
restriction,
851+
"casts between pointer-sized and fixed-size integer types may behave differently across platforms"
852+
}
853+
817854
declare_clippy_lint! {
818855
/// ### What it does
819856
/// Checks for bindings (constants, statics, or let bindings) that are defined
@@ -878,6 +915,7 @@ impl_lint_pass!(Casts => [
878915
AS_POINTER_UNDERSCORE,
879916
MANUAL_DANGLING_PTR,
880917
CONFUSING_METHOD_TO_NUMERIC_CAST,
918+
CAST_PTR_SIZED_INT,
881919
NEEDLESS_TYPE_CAST,
882920
]);
883921

@@ -923,6 +961,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
923961
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
924962
cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
925963
cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to);
964+
cast_ptr_sized_int::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
926965
}
927966
cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, self.msrv);
928967
cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from);

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
6161
crate::casts::CAST_POSSIBLE_WRAP_INFO,
6262
crate::casts::CAST_PRECISION_LOSS_INFO,
6363
crate::casts::CAST_PTR_ALIGNMENT_INFO,
64+
crate::casts::CAST_PTR_SIZED_INT_INFO,
6465
crate::casts::CAST_SIGN_LOSS_INFO,
6566
crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
6667
crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,

tests/ui/cast_ptr_sized_int.fixed

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#![warn(clippy::cast_ptr_sized_int)]
2+
#![allow(clippy::unnecessary_cast, clippy::unnecessary_fallible_conversions)]
3+
4+
fn main() {
5+
// usize to fixed-size unsigned
6+
let x: usize = 42;
7+
let _ = u8::try_from(x); //~ cast_ptr_sized_int
8+
let _ = u16::try_from(x); //~ cast_ptr_sized_int
9+
let _ = u32::try_from(x); //~ cast_ptr_sized_int
10+
let _ = u64::try_from(x); //~ cast_ptr_sized_int
11+
let _ = u128::try_from(x); //~ cast_ptr_sized_int
12+
13+
// usize to fixed-size signed
14+
let _ = i8::try_from(x); //~ cast_ptr_sized_int
15+
let _ = i16::try_from(x); //~ cast_ptr_sized_int
16+
let _ = i32::try_from(x); //~ cast_ptr_sized_int
17+
let _ = i64::try_from(x); //~ cast_ptr_sized_int
18+
let _ = i128::try_from(x); //~ cast_ptr_sized_int
19+
20+
// isize to fixed-size unsigned
21+
let y: isize = 42;
22+
let _ = u8::try_from(y); //~ cast_ptr_sized_int
23+
let _ = u16::try_from(y); //~ cast_ptr_sized_int
24+
let _ = u32::try_from(y); //~ cast_ptr_sized_int
25+
let _ = u64::try_from(y); //~ cast_ptr_sized_int
26+
let _ = u128::try_from(y); //~ cast_ptr_sized_int
27+
28+
// isize to fixed-size signed
29+
let _ = i8::try_from(y); //~ cast_ptr_sized_int
30+
let _ = i16::try_from(y); //~ cast_ptr_sized_int
31+
let _ = i32::try_from(y); //~ cast_ptr_sized_int
32+
let _ = i64::try_from(y); //~ cast_ptr_sized_int
33+
let _ = i128::try_from(y); //~ cast_ptr_sized_int
34+
35+
// fixed-size unsigned to usize
36+
let a: u8 = 1;
37+
let b: u16 = 1;
38+
let c: u32 = 1;
39+
let d: u64 = 1;
40+
let e: u128 = 1;
41+
let _ = usize::try_from(a); //~ cast_ptr_sized_int
42+
let _ = usize::try_from(b); //~ cast_ptr_sized_int
43+
let _ = usize::try_from(c); //~ cast_ptr_sized_int
44+
let _ = usize::try_from(d); //~ cast_ptr_sized_int
45+
let _ = usize::try_from(e); //~ cast_ptr_sized_int
46+
47+
// fixed-size signed to usize
48+
let f: i8 = 1;
49+
let g: i16 = 1;
50+
let h: i32 = 1;
51+
let i: i64 = 1;
52+
let j: i128 = 1;
53+
let _ = usize::try_from(f); //~ cast_ptr_sized_int
54+
let _ = usize::try_from(g); //~ cast_ptr_sized_int
55+
let _ = usize::try_from(h); //~ cast_ptr_sized_int
56+
let _ = usize::try_from(i); //~ cast_ptr_sized_int
57+
let _ = usize::try_from(j); //~ cast_ptr_sized_int
58+
59+
// fixed-size to isize
60+
let _ = isize::try_from(a); //~ cast_ptr_sized_int
61+
let _ = isize::try_from(b); //~ cast_ptr_sized_int
62+
let _ = isize::try_from(c); //~ cast_ptr_sized_int
63+
let _ = isize::try_from(d); //~ cast_ptr_sized_int
64+
let _ = isize::try_from(e); //~ cast_ptr_sized_int
65+
let _ = isize::try_from(f); //~ cast_ptr_sized_int
66+
let _ = isize::try_from(g); //~ cast_ptr_sized_int
67+
let _ = isize::try_from(h); //~ cast_ptr_sized_int
68+
let _ = isize::try_from(i); //~ cast_ptr_sized_int
69+
let _ = isize::try_from(j); //~ cast_ptr_sized_int
70+
}
71+
72+
// These should NOT trigger the lint
73+
74+
fn no_lint_same_kind() {
75+
// usize to isize (both pointer-sized, handled by other lints)
76+
let x: usize = 42;
77+
let _ = x as isize;
78+
79+
// isize to usize (both pointer-sized, handled by other lints)
80+
let y: isize = 42;
81+
let _ = y as usize;
82+
83+
// fixed-size to fixed-size (not pointer-sized, handled by other lints)
84+
let a: u32 = 1;
85+
let _ = a as u64;
86+
let _ = a as i64;
87+
88+
let b: i32 = 1;
89+
let _ = b as i64;
90+
let _ = b as u64;
91+
}

tests/ui/cast_ptr_sized_int.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#![warn(clippy::cast_ptr_sized_int)]
2+
#![allow(clippy::unnecessary_cast, clippy::unnecessary_fallible_conversions)]
3+
4+
fn main() {
5+
// usize to fixed-size unsigned
6+
let x: usize = 42;
7+
let _ = x as u8; //~ cast_ptr_sized_int
8+
let _ = x as u16; //~ cast_ptr_sized_int
9+
let _ = x as u32; //~ cast_ptr_sized_int
10+
let _ = x as u64; //~ cast_ptr_sized_int
11+
let _ = x as u128; //~ cast_ptr_sized_int
12+
13+
// usize to fixed-size signed
14+
let _ = x as i8; //~ cast_ptr_sized_int
15+
let _ = x as i16; //~ cast_ptr_sized_int
16+
let _ = x as i32; //~ cast_ptr_sized_int
17+
let _ = x as i64; //~ cast_ptr_sized_int
18+
let _ = x as i128; //~ cast_ptr_sized_int
19+
20+
// isize to fixed-size unsigned
21+
let y: isize = 42;
22+
let _ = y as u8; //~ cast_ptr_sized_int
23+
let _ = y as u16; //~ cast_ptr_sized_int
24+
let _ = y as u32; //~ cast_ptr_sized_int
25+
let _ = y as u64; //~ cast_ptr_sized_int
26+
let _ = y as u128; //~ cast_ptr_sized_int
27+
28+
// isize to fixed-size signed
29+
let _ = y as i8; //~ cast_ptr_sized_int
30+
let _ = y as i16; //~ cast_ptr_sized_int
31+
let _ = y as i32; //~ cast_ptr_sized_int
32+
let _ = y as i64; //~ cast_ptr_sized_int
33+
let _ = y as i128; //~ cast_ptr_sized_int
34+
35+
// fixed-size unsigned to usize
36+
let a: u8 = 1;
37+
let b: u16 = 1;
38+
let c: u32 = 1;
39+
let d: u64 = 1;
40+
let e: u128 = 1;
41+
let _ = a as usize; //~ cast_ptr_sized_int
42+
let _ = b as usize; //~ cast_ptr_sized_int
43+
let _ = c as usize; //~ cast_ptr_sized_int
44+
let _ = d as usize; //~ cast_ptr_sized_int
45+
let _ = e as usize; //~ cast_ptr_sized_int
46+
47+
// fixed-size signed to usize
48+
let f: i8 = 1;
49+
let g: i16 = 1;
50+
let h: i32 = 1;
51+
let i: i64 = 1;
52+
let j: i128 = 1;
53+
let _ = f as usize; //~ cast_ptr_sized_int
54+
let _ = g as usize; //~ cast_ptr_sized_int
55+
let _ = h as usize; //~ cast_ptr_sized_int
56+
let _ = i as usize; //~ cast_ptr_sized_int
57+
let _ = j as usize; //~ cast_ptr_sized_int
58+
59+
// fixed-size to isize
60+
let _ = a as isize; //~ cast_ptr_sized_int
61+
let _ = b as isize; //~ cast_ptr_sized_int
62+
let _ = c as isize; //~ cast_ptr_sized_int
63+
let _ = d as isize; //~ cast_ptr_sized_int
64+
let _ = e as isize; //~ cast_ptr_sized_int
65+
let _ = f as isize; //~ cast_ptr_sized_int
66+
let _ = g as isize; //~ cast_ptr_sized_int
67+
let _ = h as isize; //~ cast_ptr_sized_int
68+
let _ = i as isize; //~ cast_ptr_sized_int
69+
let _ = j as isize; //~ cast_ptr_sized_int
70+
}
71+
72+
// These should NOT trigger the lint
73+
74+
fn no_lint_same_kind() {
75+
// usize to isize (both pointer-sized, handled by other lints)
76+
let x: usize = 42;
77+
let _ = x as isize;
78+
79+
// isize to usize (both pointer-sized, handled by other lints)
80+
let y: isize = 42;
81+
let _ = y as usize;
82+
83+
// fixed-size to fixed-size (not pointer-sized, handled by other lints)
84+
let a: u32 = 1;
85+
let _ = a as u64;
86+
let _ = a as i64;
87+
88+
let b: i32 = 1;
89+
let _ = b as i64;
90+
let _ = b as u64;
91+
}

0 commit comments

Comments
 (0)