Skip to content

Null fn lints #10099

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

Merged
merged 10 commits into from
Dec 19, 2022
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4203,6 +4203,7 @@ Released 2018-09-13
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
[`fn_null_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_null_check
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
Expand Down Expand Up @@ -4590,6 +4591,7 @@ Released 2018-09-13
[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
[`transmute_null_to_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_null_to_fn
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::float_literal::LOSSY_FLOAT_LITERAL_INFO,
crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO,
crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
crate::fn_null_check::FN_NULL_CHECK_INFO,
crate::format::USELESS_FORMAT_INFO,
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO,
Expand Down Expand Up @@ -568,6 +569,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO,
crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO,
crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO,
crate::transmute::TRANSMUTE_NULL_TO_FN_INFO,
crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO,
crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO,
crate::transmute::TRANSMUTE_PTR_TO_REF_INFO,
Expand Down
129 changes: 129 additions & 0 deletions clippy_lints/src/fn_null_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{is_integer_literal, is_path_diagnostic_item};
use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;

declare_clippy_lint! {
/// ### What it does
/// Checks for comparing a function pointer to null.
///
/// ### Why is this bad?
/// Function pointers are assumed to not be null.
///
/// ### Example
/// ```rust,ignore
/// let fn_ptr: fn() = /* somehow obtained nullable function pointer */
///
/// if (fn_ptr as *const ()).is_null() { ... }
/// ```
/// Use instead:
/// ```rust,ignore
/// let fn_ptr: Option<fn()> = /* somehow obtained nullable function pointer */
///
/// if fn_ptr.is_none() { ... }
/// ```
#[clippy::version = "1.67.0"]
pub FN_NULL_CHECK,
correctness,
"`fn()` type assumed to be nullable"
}
declare_lint_pass!(FnNullCheck => [FN_NULL_CHECK]);

const LINT_MSG: &str = "function pointer assumed to be nullable, even though it isn't";
const HELP_MSG: &str = "try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value";

fn is_fn_ptr_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Cast(cast_expr, cast_ty) = expr.kind;
if let TyKind::Ptr(_) = cast_ty.kind;
if cx.typeck_results().expr_ty_adjusted(cast_expr).is_fn();
then {
true
} else {
false
}
}
}

impl<'tcx> LateLintPass<'tcx> for FnNullCheck {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Catching:
// (fn_ptr as *<const/mut> <ty>).is_null()
if_chain! {
if let ExprKind::MethodCall(method_name, receiver, _, _) = expr.kind;
if method_name.ident.as_str() == "is_null";
if is_fn_ptr_cast(cx, receiver);
then {
span_lint_and_help(
cx,
FN_NULL_CHECK,
expr.span,
LINT_MSG,
None,
HELP_MSG
);
}
}

if let ExprKind::Binary(op, left, right) = expr.kind
&& let BinOpKind::Eq = op.node
{
let to_check: &Expr<'_>;
if is_fn_ptr_cast(cx, left) {
to_check = right;
} else if is_fn_ptr_cast(cx, right) {
to_check = left;
} else {
return;
}

// Catching:
// (fn_ptr as *<const/mut> <ty>) == <const that evaluates to null_ptr>
let c = constant(cx, cx.typeck_results(), to_check);
if let Some((Constant::RawPtr(0), _)) = c {
span_lint_and_help(
cx,
FN_NULL_CHECK,
expr.span,
LINT_MSG,
None,
HELP_MSG
);
return;
}

// Catching:
// (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
if let ExprKind::Cast(cast_expr, _) = to_check.kind && is_integer_literal(cast_expr, 0) {
span_lint_and_help(
cx,
FN_NULL_CHECK,
expr.span,
LINT_MSG,
None,
HELP_MSG
);
return;
}

// Catching:
// (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
if let ExprKind::Call(func, []) = to_check.kind &&
is_path_diagnostic_item(cx, func, sym::ptr_null)
{
span_lint_and_help(
cx,
FN_NULL_CHECK,
expr.span,
LINT_MSG,
None,
HELP_MSG
);
}
}
}
}
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ mod explicit_write;
mod fallible_impl_from;
mod float_literal;
mod floating_point_arithmetic;
mod fn_null_check;
mod format;
mod format_args;
mod format_impl;
Expand Down Expand Up @@ -902,6 +903,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv())));
store.register_late_pass(|_| Box::new(semicolon_block::SemicolonBlock));
store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck));
// add lints here, do not remove this comment, it's used in `new_lint`
}

Expand Down
31 changes: 31 additions & 0 deletions clippy_lints/src/transmute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod transmute_float_to_int;
mod transmute_int_to_bool;
mod transmute_int_to_char;
mod transmute_int_to_float;
mod transmute_null_to_fn;
mod transmute_num_to_bytes;
mod transmute_ptr_to_ptr;
mod transmute_ptr_to_ref;
Expand Down Expand Up @@ -409,6 +410,34 @@ declare_clippy_lint! {
"transmutes from a null pointer to a reference, which is undefined behavior"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for null function pointer creation through transmute.
///
/// ### Why is this bad?
/// Creating a null function pointer is undefined behavior.
///
/// More info: https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization
///
/// ### Known problems
/// Not all cases can be detected at the moment of this writing.
/// For example, variables which hold a null pointer and are then fed to a `transmute`
/// call, aren't detectable yet.
///
/// ### Example
/// ```rust
/// let null_fn: fn() = unsafe { std::mem::transmute( std::ptr::null::<()>() ) };
/// ```
/// Use instead:
/// ```rust
/// let null_fn: Option<fn()> = None;
/// ```
#[clippy::version = "1.67.0"]
pub TRANSMUTE_NULL_TO_FN,
correctness,
"transmute results in a null function pointer, which is undefined behavior"
}

pub struct Transmute {
msrv: Msrv,
}
Expand All @@ -428,6 +457,7 @@ impl_lint_pass!(Transmute => [
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
TRANSMUTING_NULL,
TRANSMUTE_NULL_TO_FN,
]);
impl Transmute {
#[must_use]
Expand Down Expand Up @@ -461,6 +491,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmuting_null::check(cx, e, arg, to_ty)
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, &self.msrv)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
Expand Down
61 changes: 61 additions & 0 deletions clippy_lints/src/transmute/transmute_null_to_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use clippy_utils::consts::{constant_context, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{is_integer_literal, is_path_diagnostic_item};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::symbol::sym;

use super::TRANSMUTE_NULL_TO_FN;

const LINT_MSG: &str = "transmuting a known null pointer into a function pointer";
const NOTE_MSG: &str = "this transmute results in undefined behavior";
const HELP_MSG: &str =
"try wrapping your function pointer type in `Option<T>` instead, and using `None` as a null pointer value";

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>, to_ty: Ty<'tcx>) -> bool {
if !to_ty.is_fn() {
return false;
}

// Catching transmute over constants that resolve to `null`.
let mut const_eval_context = constant_context(cx, cx.typeck_results());
if let ExprKind::Path(ref _qpath) = arg.kind &&
let Some(Constant::RawPtr(0)) = const_eval_context.expr(arg)
{
span_lint_and_then(cx, TRANSMUTE_NULL_TO_FN, expr.span, LINT_MSG, |diag| {
diag.span_label(expr.span, NOTE_MSG);
diag.help(HELP_MSG);
});
return true;
}

// Catching:
// `std::mem::transmute(0 as *const i32)`
if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind && is_integer_literal(inner_expr, 0) {
span_lint_and_then(cx, TRANSMUTE_NULL_TO_FN, expr.span, LINT_MSG, |diag| {
diag.span_label(expr.span, NOTE_MSG);
diag.help(HELP_MSG);
});
return true;
}

// Catching:
// `std::mem::transmute(std::ptr::null::<i32>())`
if let ExprKind::Call(func1, []) = arg.kind &&
is_path_diagnostic_item(cx, func1, sym::ptr_null)
{
span_lint_and_then(cx, TRANSMUTE_NULL_TO_FN, expr.span, LINT_MSG, |diag| {
diag.span_label(expr.span, NOTE_MSG);
diag.help(HELP_MSG);
});
return true;
}

// FIXME:
// Also catch transmutations of variables which are known nulls.
// To do this, MIR const propagation seems to be the better tool.
// Whenever MIR const prop routines are more developed, this will
// become available. As of this writing (25/03/19) it is not yet.
false
}
3 changes: 1 addition & 2 deletions clippy_lints/src/transmute/transmuting_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
// Catching transmute over constants that resolve to `null`.
let mut const_eval_context = constant_context(cx, cx.typeck_results());
if let ExprKind::Path(ref _qpath) = arg.kind &&
let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg) &&
x == 0
let Some(Constant::RawPtr(0)) = const_eval_context.expr(arg)
{
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
return true;
Expand Down
7 changes: 1 addition & 6 deletions clippy_utils/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,12 +620,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) -
ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(
int.try_into().expect("invalid f64 bit representation"),
))),
ty::RawPtr(type_and_mut) => {
if let ty::Uint(_) = type_and_mut.ty.kind() {
return Some(Constant::RawPtr(int.assert_bits(int.size())));
}
None
},
ty::RawPtr(_) => Some(Constant::RawPtr(int.assert_bits(int.size()))),
// FIXME: implement other conversions.
_ => None,
}
Expand Down
21 changes: 21 additions & 0 deletions tests/ui/fn_null_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![allow(unused)]
#![warn(clippy::fn_null_check)]
#![allow(clippy::cmp_null)]
#![allow(clippy::ptr_eq)]
#![allow(clippy::zero_ptr)]

pub const ZPTR: *const () = 0 as *const _;
pub const NOT_ZPTR: *const () = 1 as *const _;

fn main() {
let fn_ptr = main;

if (fn_ptr as *mut ()).is_null() {}
if (fn_ptr as *const u8).is_null() {}
if (fn_ptr as *const ()) == std::ptr::null() {}
if (fn_ptr as *const ()) == (0 as *const ()) {}
if (fn_ptr as *const ()) == ZPTR {}

// no lint
if (fn_ptr as *const ()) == NOT_ZPTR {}
}
43 changes: 43 additions & 0 deletions tests/ui/fn_null_check.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
error: function pointer assumed to be nullable, even though it isn't
--> $DIR/fn_null_check.rs:13:8
|
LL | if (fn_ptr as *mut ()).is_null() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value
= note: `-D clippy::fn-null-check` implied by `-D warnings`

error: function pointer assumed to be nullable, even though it isn't
--> $DIR/fn_null_check.rs:14:8
|
LL | if (fn_ptr as *const u8).is_null() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value

error: function pointer assumed to be nullable, even though it isn't
--> $DIR/fn_null_check.rs:15:8
|
LL | if (fn_ptr as *const ()) == std::ptr::null() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value

error: function pointer assumed to be nullable, even though it isn't
--> $DIR/fn_null_check.rs:16:8
|
LL | if (fn_ptr as *const ()) == (0 as *const ()) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value

error: function pointer assumed to be nullable, even though it isn't
--> $DIR/fn_null_check.rs:17:8
|
LL | if (fn_ptr as *const ()) == ZPTR {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try wrapping your function pointer type in `Option<T>` instead, and using `is_none` to check for null pointer value

error: aborting due to 5 previous errors

Loading