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 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 = /* somehow obtained nullable function pointer */ /// /// if fn_ptr.is_none() { ... } /// ``` #[clippy::version = "1.68.0"] pub FN_NULL_CHECK, correctness, "`fn()` type assumed to be nullable" } declare_lint_pass!(FnNullCheck => [FN_NULL_CHECK]); fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) { span_lint_and_help( cx, FN_NULL_CHECK, expr.span, "function pointer assumed to be nullable, even though it isn't", None, "try wrapping your function pointer type in `Option` instead, and using `is_none` to check for null pointer value", ); } fn is_fn_ptr_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::Cast(cast_expr, cast_ty) = expr.kind && let TyKind::Ptr(_) = cast_ty.kind { cx.typeck_results().expr_ty_adjusted(cast_expr).is_fn() } else { false } } impl<'tcx> LateLintPass<'tcx> for FnNullCheck { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { match expr.kind { // Catching: // (fn_ptr as * ).is_null() ExprKind::MethodCall(method_name, receiver, _, _) if method_name.ident.as_str() == "is_null" && is_fn_ptr_cast(cx, receiver) => { lint_expr(cx, expr); }, ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { 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; } match to_check.kind { // Catching: // (fn_ptr as * ) == (0 as ) ExprKind::Cast(cast_expr, _) if is_integer_literal(cast_expr, 0) => { lint_expr(cx, expr); }, // Catching: // (fn_ptr as * ) == std::ptr::null() ExprKind::Call(func, []) if is_path_diagnostic_item(cx, func, sym::ptr_null) => { lint_expr(cx, expr); }, // Catching: // (fn_ptr as * ) == _ if matches!(constant(cx, cx.typeck_results(), to_check), Some(Constant::RawPtr(0))) => { lint_expr(cx, expr); }, _ => {}, } }, _ => {}, } } }