rustc_lint/
ptr_nulls.rs

1use rustc_ast::LitKind;
2use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
3use rustc_session::{declare_lint, declare_lint_pass};
4use rustc_span::sym;
5
6use crate::lints::PtrNullChecksDiag;
7use crate::{LateContext, LateLintPass, LintContext};
8
9declare_lint! {
10    /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers
11    /// obtained from non-null types.
12    ///
13    /// ### Example
14    ///
15    /// ```rust
16    /// # fn test() {}
17    /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */
18    /// #   test;
19    ///
20    /// if (fn_ptr as *const ()).is_null() { /* ... */ }
21    /// ```
22    ///
23    /// {{produces}}
24    ///
25    /// ### Explanation
26    ///
27    /// Function pointers and references are assumed to be non-null, checking them for null
28    /// will always return false.
29    USELESS_PTR_NULL_CHECKS,
30    Warn,
31    "useless checking of non-null-typed pointer"
32}
33
34declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]);
35
36/// This function checks if the expression is from a series of consecutive casts,
37/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
38/// a fn ptr, a reference, or a function call whose definition is
39/// annotated with `#![rustc_never_returns_null_ptr]`.
40/// If this situation is present, the function returns the appropriate diagnostic.
41fn incorrect_check<'a, 'tcx: 'a>(
42    cx: &'a LateContext<'tcx>,
43    mut e: &'a Expr<'a>,
44) -> Option<PtrNullChecksDiag<'tcx>> {
45    let mut had_at_least_one_cast = false;
46    loop {
47        e = e.peel_blocks();
48        if let ExprKind::MethodCall(_, _expr, [], _) = e.kind
49            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
50            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
51            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
52        {
53            return Some(PtrNullChecksDiag::FnRet { fn_name });
54        } else if let ExprKind::Call(path, _args) = e.kind
55            && let ExprKind::Path(ref qpath) = path.kind
56            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
57            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
58            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
59        {
60            return Some(PtrNullChecksDiag::FnRet { fn_name });
61        }
62        e = if let ExprKind::Cast(expr, t) = e.kind
63            && let TyKind::Ptr(_) = t.kind
64        {
65            had_at_least_one_cast = true;
66            expr
67        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
68            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
69            && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut))
70        {
71            had_at_least_one_cast = true;
72            expr
73        } else if had_at_least_one_cast {
74            let orig_ty = cx.typeck_results().expr_ty(e);
75            return if orig_ty.is_fn() {
76                Some(PtrNullChecksDiag::FnPtr { orig_ty, label: e.span })
77            } else if orig_ty.is_ref() {
78                Some(PtrNullChecksDiag::Ref { orig_ty, label: e.span })
79            } else {
80                None
81            };
82        } else {
83            return None;
84        };
85    }
86}
87
88impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
89    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
90        match expr.kind {
91            // Catching:
92            // <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>)
93            ExprKind::Call(path, [arg])
94                if let ExprKind::Path(ref qpath) = path.kind
95                    && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
96                    && matches!(
97                        cx.tcx.get_diagnostic_name(def_id),
98                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
99                    )
100                    && let Some(diag) = incorrect_check(cx, arg) =>
101            {
102                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
103            }
104
105            // Catching:
106            // (fn_ptr as *<const/mut> <ty>).is_null()
107            ExprKind::MethodCall(_, receiver, _, _)
108                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
109                    && matches!(
110                        cx.tcx.get_diagnostic_name(def_id),
111                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
112                    )
113                    && let Some(diag) = incorrect_check(cx, receiver) =>
114            {
115                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
116            }
117
118            ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
119                let to_check: &Expr<'_>;
120                let diag: PtrNullChecksDiag<'_>;
121                if let Some(ddiag) = incorrect_check(cx, left) {
122                    to_check = right;
123                    diag = ddiag;
124                } else if let Some(ddiag) = incorrect_check(cx, right) {
125                    to_check = left;
126                    diag = ddiag;
127                } else {
128                    return;
129                }
130
131                match to_check.kind {
132                    // Catching:
133                    // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
134                    ExprKind::Cast(cast_expr, _)
135                        if let ExprKind::Lit(spanned) = cast_expr.kind
136                            && let LitKind::Int(v, _) = spanned.node
137                            && v == 0 =>
138                    {
139                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
140                    }
141
142                    // Catching:
143                    // (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
144                    ExprKind::Call(path, [])
145                        if let ExprKind::Path(ref qpath) = path.kind
146                            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
147                            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
148                            && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
149                    {
150                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
151                    }
152
153                    _ => {}
154                }
155            }
156            _ => {}
157        }
158    }
159}