Skip to content

Implement new lint for detecting buggy pointer-to-int casts #81789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -2876,6 +2876,46 @@ declare_lint! {
};
}

declare_lint! {
/// The `invalid_ptr_to_int_cast` lint triggers if a pointer is cast to any integer type other
/// than `usize` or `u64`, since doing so is often a bug.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(invalid_ptr_to_int_cast)]
///
/// fn main() {
/// let x = 100_000_000;
/// let y = u16::max as u32; // the user meant the constant `u16::MAX`, rather than the
/// // function `u16::max`, but this cast is technically permitted,
/// // so will not produce an error
/// println!("{}", x > y); // prints `false` (unexpectedly)
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// The example above shows how a user might accidentally cast a function pointer (rather than
/// an integer constant) to an integer type other than `usize` or `u64`. Though this is
/// currently permitted by Rust, there is very little reason to cast a pointer to any integer
/// other than a `usize` or `u64`. Therefore, any instances of such casts are likely to be
/// bugs.
///
/// This lint warns against those cases.
///
/// In the future, we may want to make this a hard error.
///
/// To cast a pointer to an integer type other than `usize` or `u64` without triggering the
/// lint, you can first cast to a `usize` and then to the integer type, e.g. `ptr as usize as
/// u32`.
pub INVALID_PTR_TO_INT_CAST,
Deny,
"detects pointers casts to integer types other than `usize` or `u64`",
}

declare_lint_pass! {
/// Does nothing as a lint pass, but registers some `Lint`s
/// that are used by other parts of the compiler.
@@ -2960,6 +3000,7 @@ declare_lint_pass! {
LEGACY_DERIVE_HELPERS,
PROC_MACRO_BACK_COMPAT,
OR_PATTERNS_BACK_COMPAT,
INVALID_PTR_TO_INT_CAST,
]
}

5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
@@ -1946,6 +1946,11 @@ impl<'tcx> TyS<'tcx> {
matches!(self.kind(), FnPtr(_))
}

#[inline]
pub fn is_raw_ptr(&self) -> bool {
matches!(self.kind(), RawPtr(_))
}

#[inline]
pub fn is_impl_trait(&self) -> bool {
matches!(self.kind(), Opaque(..))
121 changes: 84 additions & 37 deletions compiler/rustc_typeck/src/check/cast.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
@@ -995,7 +995,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
// Defer other checks until we're done type checking.
let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut();
match cast::CastCheck::new(self, e, t_expr, t_cast, t.span, expr.span) {
match cast::CastCheck::new(self, expr, e, t_expr, t_cast, t.span) {
Ok(cast_check) => {
deferred_cast_checks.push(cast_check);
t_cast
42 changes: 42 additions & 0 deletions src/test/ui/lint/lint-invalid-ptr-to-int-cast.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// check-pass
// run-rustfix
// compile-flags: -W invalid-ptr-to-int-cast

fn main() {
// Motivation for this lint: user meant u16::MAX
let _ = u16::max as usize as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]

// Pointer to int
let a: usize = 123;
let ptr: *const usize = &a as *const usize;

let _ = ptr as usize as u8; //~ casting pointer to `u8` [invalid_ptr_to_int_cast]
let _ = ptr as usize as u16; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
let _ = ptr as usize as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]
let _ = ptr as u64;
let _ = ptr as usize as u128; //~ casting pointer to `u128` [invalid_ptr_to_int_cast]
let _ = ptr as usize as i8; //~ casting pointer to `i8` [invalid_ptr_to_int_cast]
let _ = ptr as usize as i16; //~ casting pointer to `i16` [invalid_ptr_to_int_cast]
let _ = ptr as usize as i32; //~ casting pointer to `i32` [invalid_ptr_to_int_cast]
let _ = ptr as usize as i64; //~ casting pointer to `i64` [invalid_ptr_to_int_cast]
let _ = ptr as usize as i128; //~ casting pointer to `i128` [invalid_ptr_to_int_cast]
let _ = ptr as usize;

// Function to int
fn test() {}

let _ = test as usize as u8; //~ casting pointer to `u8` [invalid_ptr_to_int_cast]
let _ = test as usize as u16; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
let _ = test as usize as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]
let _ = test as u64;
let _ = test as usize as u128; //~ casting pointer to `u128` [invalid_ptr_to_int_cast]
let _ = test as usize as i8; //~ casting pointer to `i8` [invalid_ptr_to_int_cast]
let _ = test as usize as i16; //~ casting pointer to `i16` [invalid_ptr_to_int_cast]
let _ = test as usize as i32; //~ casting pointer to `i32` [invalid_ptr_to_int_cast]
let _ = test as usize as i64; //~ casting pointer to `i64` [invalid_ptr_to_int_cast]
let _ = test as usize as i128; //~ casting pointer to `i128` [invalid_ptr_to_int_cast]
let _ = test as usize;

// Make sure we handle delayed coercion cast checking
let _ = {({ test }) as usize as u16}; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
}
42 changes: 42 additions & 0 deletions src/test/ui/lint/lint-invalid-ptr-to-int-cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// check-pass
// run-rustfix
// compile-flags: -W invalid-ptr-to-int-cast

fn main() {
// Motivation for this lint: user meant u16::MAX
let _ = u16::max as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]

// Pointer to int
let a: usize = 123;
let ptr: *const usize = &a as *const usize;

let _ = ptr as u8; //~ casting pointer to `u8` [invalid_ptr_to_int_cast]
let _ = ptr as u16; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
let _ = ptr as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]
let _ = ptr as u64;
let _ = ptr as u128; //~ casting pointer to `u128` [invalid_ptr_to_int_cast]
let _ = ptr as i8; //~ casting pointer to `i8` [invalid_ptr_to_int_cast]
let _ = ptr as i16; //~ casting pointer to `i16` [invalid_ptr_to_int_cast]
let _ = ptr as i32; //~ casting pointer to `i32` [invalid_ptr_to_int_cast]
let _ = ptr as i64; //~ casting pointer to `i64` [invalid_ptr_to_int_cast]
let _ = ptr as i128; //~ casting pointer to `i128` [invalid_ptr_to_int_cast]
let _ = ptr as usize;

// Function to int
fn test() {}

let _ = test as u8; //~ casting pointer to `u8` [invalid_ptr_to_int_cast]
let _ = test as u16; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
let _ = test as u32; //~ casting pointer to `u32` [invalid_ptr_to_int_cast]
let _ = test as u64;
let _ = test as u128; //~ casting pointer to `u128` [invalid_ptr_to_int_cast]
let _ = test as i8; //~ casting pointer to `i8` [invalid_ptr_to_int_cast]
let _ = test as i16; //~ casting pointer to `i16` [invalid_ptr_to_int_cast]
let _ = test as i32; //~ casting pointer to `i32` [invalid_ptr_to_int_cast]
let _ = test as i64; //~ casting pointer to `i64` [invalid_ptr_to_int_cast]
let _ = test as i128; //~ casting pointer to `i128` [invalid_ptr_to_int_cast]
let _ = test as usize;

// Make sure we handle delayed coercion cast checking
let _ = {({ test }) as u16}; //~ casting pointer to `u16` [invalid_ptr_to_int_cast]
}
163 changes: 163 additions & 0 deletions src/test/ui/lint/lint-invalid-ptr-to-int-cast.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
warning: casting pointer to `u32`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:7:13
|
LL | let _ = u16::max as u32;
| ^^^^^^^^^^^^^^^ help: to cast to `u32`, cast to `usize` first: `u16::max as usize as u32`
|
= note: requested on the command line with `-W invalid-ptr-to-int-cast`
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u8`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:13:13
|
LL | let _ = ptr as u8;
| ^^^^^^^^^ help: to cast to `u8`, cast to `usize` first: `ptr as usize as u8`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u16`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:14:13
|
LL | let _ = ptr as u16;
| ^^^^^^^^^^ help: to cast to `u16`, cast to `usize` first: `ptr as usize as u16`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u32`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:15:13
|
LL | let _ = ptr as u32;
| ^^^^^^^^^^ help: to cast to `u32`, cast to `usize` first: `ptr as usize as u32`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u128`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:17:13
|
LL | let _ = ptr as u128;
| ^^^^^^^^^^^ help: to cast to `u128`, cast to `usize` first: `ptr as usize as u128`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i8`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:18:13
|
LL | let _ = ptr as i8;
| ^^^^^^^^^ help: to cast to `i8`, cast to `usize` first: `ptr as usize as i8`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i16`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:19:13
|
LL | let _ = ptr as i16;
| ^^^^^^^^^^ help: to cast to `i16`, cast to `usize` first: `ptr as usize as i16`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i32`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:20:13
|
LL | let _ = ptr as i32;
| ^^^^^^^^^^ help: to cast to `i32`, cast to `usize` first: `ptr as usize as i32`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i64`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:21:13
|
LL | let _ = ptr as i64;
| ^^^^^^^^^^ help: to cast to `i64`, cast to `usize` first: `ptr as usize as i64`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i128`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:22:13
|
LL | let _ = ptr as i128;
| ^^^^^^^^^^^ help: to cast to `i128`, cast to `usize` first: `ptr as usize as i128`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u8`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:28:13
|
LL | let _ = test as u8;
| ^^^^^^^^^^ help: to cast to `u8`, cast to `usize` first: `test as usize as u8`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u16`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:29:13
|
LL | let _ = test as u16;
| ^^^^^^^^^^^ help: to cast to `u16`, cast to `usize` first: `test as usize as u16`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u32`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:30:13
|
LL | let _ = test as u32;
| ^^^^^^^^^^^ help: to cast to `u32`, cast to `usize` first: `test as usize as u32`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u128`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:32:13
|
LL | let _ = test as u128;
| ^^^^^^^^^^^^ help: to cast to `u128`, cast to `usize` first: `test as usize as u128`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i8`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:33:13
|
LL | let _ = test as i8;
| ^^^^^^^^^^ help: to cast to `i8`, cast to `usize` first: `test as usize as i8`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i16`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:34:13
|
LL | let _ = test as i16;
| ^^^^^^^^^^^ help: to cast to `i16`, cast to `usize` first: `test as usize as i16`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i32`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:35:13
|
LL | let _ = test as i32;
| ^^^^^^^^^^^ help: to cast to `i32`, cast to `usize` first: `test as usize as i32`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i64`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:36:13
|
LL | let _ = test as i64;
| ^^^^^^^^^^^ help: to cast to `i64`, cast to `usize` first: `test as usize as i64`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `i128`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:37:13
|
LL | let _ = test as i128;
| ^^^^^^^^^^^^ help: to cast to `i128`, cast to `usize` first: `test as usize as i128`
|
= help: pointers should only be cast to `usize` or `u64`

warning: casting pointer to `u16`
--> $DIR/lint-invalid-ptr-to-int-cast.rs:41:14
|
LL | let _ = {({ test }) as u16};
| ^^^^^^^^^^^^^^^^^ help: to cast to `u16`, cast to `usize` first: `({ test }) as usize as u16`
|
= help: pointers should only be cast to `usize` or `u64`

warning: 20 warnings emitted

7 changes: 4 additions & 3 deletions src/tools/clippy/clippy_lints/src/transmute/utils.rs
Original file line number Diff line number Diff line change
@@ -84,9 +84,10 @@ fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>
);

if let Ok(check) = CastCheck::new(
&fn_ctxt, e, from_ty, to_ty,
// We won't show any error to the user, so we don't care what the span is here.
DUMMY_SP, DUMMY_SP,
// We won't show any error to the user, so we don't care about the span and expressions
// here
&fn_ctxt, e, e, from_ty, to_ty,
DUMMY_SP,
) {
let res = check.do_check(&fn_ctxt);