Skip to content

Commit 6078a36

Browse files
committed
Restriction lint for function pointer casts
1 parent 685b773 commit 6078a36

File tree

6 files changed

+258
-0
lines changed

6 files changed

+258
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2727,6 +2727,7 @@ Released 2018-09-13
27272727
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
27282728
[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
27292729
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
2730+
[`fn_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_cast
27302731
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
27312732
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
27322733
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation

clippy_lints/src/casts/fn_cast.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::Expr;
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{self, Ty};
7+
8+
use super::FN_CAST;
9+
10+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
11+
// We allow casts from any function type to any function type.
12+
match cast_to.kind() {
13+
ty::FnDef(..) | ty::FnPtr(..) => return,
14+
_ => { /* continue to checks */ },
15+
}
16+
17+
match cast_from.kind() {
18+
ty::FnDef(..) | ty::FnPtr(_) => {
19+
let mut applicability = Applicability::MaybeIncorrect;
20+
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
21+
22+
span_lint_and_sugg(
23+
cx,
24+
FN_CAST,
25+
expr.span,
26+
&format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
27+
"did you mean to invoke the function?",
28+
format!("{}() as {}", from_snippet, cast_to),
29+
applicability,
30+
);
31+
},
32+
_ => {},
33+
}
34+
}

clippy_lints/src/casts/mod.rs

+39
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod cast_ptr_alignment;
66
mod cast_ref_to_mut;
77
mod cast_sign_loss;
88
mod char_lit_as_u8;
9+
mod fn_cast;
910
mod fn_to_numeric_cast;
1011
mod fn_to_numeric_cast_with_truncation;
1112
mod ptr_as_ptr;
@@ -251,6 +252,42 @@ declare_clippy_lint! {
251252
"casting a function pointer to a numeric type not wide enough to store the address"
252253
}
253254

255+
declare_clippy_lint! {
256+
/// ### What it does
257+
/// Checks for casts of a function pointer to any non-function type.
258+
///
259+
/// ### Why is this bad?
260+
/// Casting a function pointer can have surprising results and can occur accidentally if
261+
/// parantheses are omitted from a function call. If you aren't doing anything low-level with
262+
/// function pointers then you can opt-out of casting functions to avoid mistakes.
263+
/// Alternatively, you can use this lint to audit all uses of function pointer casts in your
264+
/// code.
265+
///
266+
/// ### Example
267+
/// ```rust
268+
/// // Bad: fn1 is cast as `usize`
269+
/// fn fn1() -> u16 {
270+
/// 1
271+
/// };
272+
/// let _ = fn1 as usize;
273+
///
274+
/// // Good: maybe you intended to call the function?
275+
/// fn fn2() -> u16 {
276+
/// 1
277+
/// };
278+
/// let _ = fn2() as usize;
279+
///
280+
/// // Good: maybe you intended to cast it to a function type?
281+
/// fn fn3() -> u16 {
282+
/// 1
283+
/// }
284+
/// let _ = fn3 as fn() -> u16;
285+
/// ```
286+
pub FN_CAST,
287+
restriction,
288+
"casting a function pointer to any non-function type"
289+
}
290+
254291
declare_clippy_lint! {
255292
/// ### What it does
256293
/// Checks for casts of `&T` to `&mut T` anywhere in the code.
@@ -360,6 +397,7 @@ impl_lint_pass!(Casts => [
360397
CAST_REF_TO_MUT,
361398
CAST_PTR_ALIGNMENT,
362399
UNNECESSARY_CAST,
400+
FN_CAST,
363401
FN_TO_NUMERIC_CAST,
364402
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
365403
CHAR_LIT_AS_U8,
@@ -385,6 +423,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
385423
return;
386424
}
387425

426+
fn_cast::check(cx, expr, cast_expr, cast_from, cast_to);
388427
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
389428
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
390429
if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
570570
casts::CAST_REF_TO_MUT,
571571
casts::CAST_SIGN_LOSS,
572572
casts::CHAR_LIT_AS_U8,
573+
casts::FN_CAST,
573574
casts::FN_TO_NUMERIC_CAST,
574575
casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
575576
casts::PTR_AS_PTR,
@@ -1018,6 +1019,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10181019
LintId::of(as_conversions::AS_CONVERSIONS),
10191020
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
10201021
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
1022+
LintId::of(casts::FN_CAST),
10211023
LintId::of(create_dir::CREATE_DIR),
10221024
LintId::of(dbg_macro::DBG_MACRO),
10231025
LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),

tests/ui/fn_cast.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#![warn(clippy::fn_cast)]
2+
#![allow(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)]
3+
4+
fn foo() -> u8 {
5+
0
6+
}
7+
8+
fn generic_foo<T>(x: T) -> T {
9+
x
10+
}
11+
12+
trait Trait {
13+
fn static_method() -> u32 {
14+
2
15+
}
16+
}
17+
18+
struct Struct;
19+
20+
impl Trait for Struct {}
21+
22+
fn fn_pointer_to_integer() {
23+
let _ = foo as i8;
24+
let _ = foo as i16;
25+
let _ = foo as i32;
26+
let _ = foo as i64;
27+
let _ = foo as i128;
28+
let _ = foo as isize;
29+
30+
let _ = foo as u8;
31+
let _ = foo as u16;
32+
let _ = foo as u32;
33+
let _ = foo as u64;
34+
let _ = foo as u128;
35+
let _ = foo as usize;
36+
}
37+
38+
fn static_method_to_integer() {
39+
let _ = Struct::static_method as usize;
40+
}
41+
42+
fn fn_with_fn_arg(f: fn(i32) -> u32) -> usize {
43+
f as usize
44+
}
45+
46+
fn fn_with_generic_static_trait_method<T: Trait>() -> usize {
47+
T::static_method as usize
48+
}
49+
50+
fn closure_to_fn_to_integer() {
51+
let clos = |x| x * 2_u32;
52+
53+
let _ = (clos as fn(u32) -> u32) as usize;
54+
}
55+
56+
fn fn_to_raw_ptr() {
57+
let _ = foo as *const ();
58+
}
59+
60+
fn cast_fn_to_self() {
61+
// Casting to the same function pointer type should be permitted.
62+
let _ = foo as fn() -> u8;
63+
}
64+
65+
fn cast_generic_to_concrete() {
66+
// Casting to a more concrete function pointer type should be permitted.
67+
let _ = generic_foo as fn(usize) -> usize;
68+
}
69+
70+
fn cast_closure_to_fn() {
71+
// Casting a closure to a function pointer should be permitted.
72+
let id = |x| x;
73+
let _ = id as fn(usize) -> usize;
74+
}
75+
76+
fn main() {}

tests/ui/fn_cast.stderr

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
error: casting function pointer `foo` to `i8`
2+
--> $DIR/fn_cast.rs:23:13
3+
|
4+
LL | let _ = foo as i8;
5+
| ^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i8`
6+
|
7+
= note: `-D clippy::fn-cast` implied by `-D warnings`
8+
9+
error: casting function pointer `foo` to `i16`
10+
--> $DIR/fn_cast.rs:24:13
11+
|
12+
LL | let _ = foo as i16;
13+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i16`
14+
15+
error: casting function pointer `foo` to `i32`
16+
--> $DIR/fn_cast.rs:25:13
17+
|
18+
LL | let _ = foo as i32;
19+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i32`
20+
21+
error: casting function pointer `foo` to `i64`
22+
--> $DIR/fn_cast.rs:26:13
23+
|
24+
LL | let _ = foo as i64;
25+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i64`
26+
27+
error: casting function pointer `foo` to `i128`
28+
--> $DIR/fn_cast.rs:27:13
29+
|
30+
LL | let _ = foo as i128;
31+
| ^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i128`
32+
33+
error: casting function pointer `foo` to `isize`
34+
--> $DIR/fn_cast.rs:28:13
35+
|
36+
LL | let _ = foo as isize;
37+
| ^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as isize`
38+
39+
error: casting function pointer `foo` to `u8`
40+
--> $DIR/fn_cast.rs:30:13
41+
|
42+
LL | let _ = foo as u8;
43+
| ^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u8`
44+
45+
error: casting function pointer `foo` to `u16`
46+
--> $DIR/fn_cast.rs:31:13
47+
|
48+
LL | let _ = foo as u16;
49+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u16`
50+
51+
error: casting function pointer `foo` to `u32`
52+
--> $DIR/fn_cast.rs:32:13
53+
|
54+
LL | let _ = foo as u32;
55+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u32`
56+
57+
error: casting function pointer `foo` to `u64`
58+
--> $DIR/fn_cast.rs:33:13
59+
|
60+
LL | let _ = foo as u64;
61+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u64`
62+
63+
error: casting function pointer `foo` to `u128`
64+
--> $DIR/fn_cast.rs:34:13
65+
|
66+
LL | let _ = foo as u128;
67+
| ^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u128`
68+
69+
error: casting function pointer `foo` to `usize`
70+
--> $DIR/fn_cast.rs:35:13
71+
|
72+
LL | let _ = foo as usize;
73+
| ^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as usize`
74+
75+
error: casting function pointer `Struct::static_method` to `usize`
76+
--> $DIR/fn_cast.rs:39:13
77+
|
78+
LL | let _ = Struct::static_method as usize;
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `Struct::static_method() as usize`
80+
81+
error: casting function pointer `f` to `usize`
82+
--> $DIR/fn_cast.rs:43:5
83+
|
84+
LL | f as usize
85+
| ^^^^^^^^^^ help: did you mean to invoke the function?: `f() as usize`
86+
87+
error: casting function pointer `T::static_method` to `usize`
88+
--> $DIR/fn_cast.rs:47:5
89+
|
90+
LL | T::static_method as usize
91+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `T::static_method() as usize`
92+
93+
error: casting function pointer `(clos as fn(u32) -> u32)` to `usize`
94+
--> $DIR/fn_cast.rs:53:13
95+
|
96+
LL | let _ = (clos as fn(u32) -> u32) as usize;
97+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `(clos as fn(u32) -> u32)() as usize`
98+
99+
error: casting function pointer `foo` to `*const ()`
100+
--> $DIR/fn_cast.rs:57:13
101+
|
102+
LL | let _ = foo as *const ();
103+
| ^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as *const ()`
104+
105+
error: aborting due to 17 previous errors
106+

0 commit comments

Comments
 (0)