rustc_lint/
ptr_nulls.rs

1use rustc_ast::LitKind;
2use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
3use rustc_middle::ty::RawPtr;
4use rustc_session::{declare_lint, declare_lint_pass};
5use rustc_span::{Span, sym};
6
7use crate::lints::{InvalidNullArgumentsDiag, UselessPtrNullChecksDiag};
8use crate::utils::peel_casts;
9use crate::{LateContext, LateLintPass, LintContext};
10
11declare_lint! {
12    /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers
13    /// obtained from non-null types.
14    ///
15    /// ### Example
16    ///
17    /// ```rust
18    /// # fn test() {}
19    /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */
20    /// #   test;
21    ///
22    /// if (fn_ptr as *const ()).is_null() { /* ... */ }
23    /// ```
24    ///
25    /// {{produces}}
26    ///
27    /// ### Explanation
28    ///
29    /// Function pointers and references are assumed to be non-null, checking them for null
30    /// will always return false.
31    USELESS_PTR_NULL_CHECKS,
32    Warn,
33    "useless checking of non-null-typed pointer"
34}
35
36declare_lint! {
37    /// The `invalid_null_arguments` lint checks for invalid usage of null pointers in arguments.
38    ///
39    /// ### Example
40    ///
41    /// ```rust,compile_fail
42    /// # use std::{slice, ptr};
43    /// // Undefined behavior
44    /// # let _slice: &[u8] =
45    /// unsafe { slice::from_raw_parts(ptr::null(), 0) };
46    /// ```
47    ///
48    /// {{produces}}
49    ///
50    /// ### Explanation
51    ///
52    /// Calling methods whos safety invariants requires non-null ptr with a null pointer
53    /// is [Undefined Behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html)!
54    INVALID_NULL_ARGUMENTS,
55    Deny,
56    "invalid null pointer in arguments"
57}
58
59declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS, INVALID_NULL_ARGUMENTS]);
60
61/// This function checks if the expression is from a series of consecutive casts,
62/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
63/// a fn ptr, a reference, or a function call whose definition is
64/// annotated with `#![rustc_never_returns_null_ptr]`.
65/// If this situation is present, the function returns the appropriate diagnostic.
66fn useless_check<'a, 'tcx: 'a>(
67    cx: &'a LateContext<'tcx>,
68    mut e: &'a Expr<'a>,
69) -> Option<UselessPtrNullChecksDiag<'tcx>> {
70    let mut had_at_least_one_cast = false;
71    loop {
72        e = e.peel_blocks();
73        if let ExprKind::MethodCall(_, _expr, [], _) = e.kind
74            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
75            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
76            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
77        {
78            return Some(UselessPtrNullChecksDiag::FnRet { fn_name });
79        } else if let ExprKind::Call(path, _args) = e.kind
80            && let ExprKind::Path(ref qpath) = path.kind
81            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
82            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
83            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
84        {
85            return Some(UselessPtrNullChecksDiag::FnRet { fn_name });
86        }
87        e = if let ExprKind::Cast(expr, t) = e.kind
88            && let TyKind::Ptr(_) = t.kind
89        {
90            had_at_least_one_cast = true;
91            expr
92        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
93            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
94            && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut))
95        {
96            had_at_least_one_cast = true;
97            expr
98        } else if had_at_least_one_cast {
99            let orig_ty = cx.typeck_results().expr_ty(e);
100            return if orig_ty.is_fn() {
101                Some(UselessPtrNullChecksDiag::FnPtr { orig_ty, label: e.span })
102            } else if orig_ty.is_ref() {
103                Some(UselessPtrNullChecksDiag::Ref { orig_ty, label: e.span })
104            } else {
105                None
106            };
107        } else {
108            return None;
109        };
110    }
111}
112
113/// Checks if the given expression is a null pointer (modulo casting)
114fn is_null_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Span> {
115    let (expr, _) = peel_casts(cx, expr);
116
117    if let ExprKind::Call(path, []) = expr.kind
118        && let ExprKind::Path(ref qpath) = path.kind
119        && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
120        && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
121    {
122        (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut).then_some(expr.span)
123    } else if let ExprKind::Lit(spanned) = expr.kind
124        && let LitKind::Int(v, _) = spanned.node
125    {
126        (v == 0).then_some(expr.span)
127    } else {
128        None
129    }
130}
131
132impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
133    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
134        match expr.kind {
135            // Catching:
136            // <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>)
137            ExprKind::Call(path, [arg])
138                if let ExprKind::Path(ref qpath) = path.kind
139                    && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
140                    && matches!(
141                        cx.tcx.get_diagnostic_name(def_id),
142                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
143                    )
144                    && let Some(diag) = useless_check(cx, arg) =>
145            {
146                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
147            }
148
149            // Catching:
150            // <path>(arg...) where `arg` is null-ptr and `path` is a fn that expect non-null-ptr
151            ExprKind::Call(path, args)
152                if let ExprKind::Path(ref qpath) = path.kind
153                    && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
154                    && let Some(diag_name) = cx.tcx.get_diagnostic_name(def_id) =>
155            {
156                // `arg` positions where null would cause U.B and whenever ZST are allowed.
157                //
158                // We should probably have a `rustc` attribute, but checking them is costly,
159                // maybe if we checked for null ptr first, it would be acceptable?
160                let (arg_indices, are_zsts_allowed): (&[_], _) = match diag_name {
161                    sym::ptr_read
162                    | sym::ptr_read_unaligned
163                    | sym::ptr_read_volatile
164                    | sym::ptr_replace
165                    | sym::ptr_write
166                    | sym::ptr_write_bytes
167                    | sym::ptr_write_unaligned
168                    | sym::ptr_write_volatile => (&[0], true),
169                    sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut => (&[0], false),
170                    sym::ptr_copy
171                    | sym::ptr_copy_nonoverlapping
172                    | sym::ptr_swap
173                    | sym::ptr_swap_nonoverlapping => (&[0, 1], true),
174                    _ => return,
175                };
176
177                for &arg_idx in arg_indices {
178                    if let Some(arg) = args.get(arg_idx)
179                        && let Some(null_span) = is_null_ptr(cx, arg)
180                        && let Some(ty) = cx.typeck_results().expr_ty_opt(arg)
181                        && let RawPtr(ty, _mutbl) = ty.kind()
182                    {
183                        // If ZST are fine, don't lint on them
184                        let typing_env = cx.typing_env();
185                        if are_zsts_allowed
186                            && cx
187                                .tcx
188                                .layout_of(typing_env.as_query_input(*ty))
189                                .is_ok_and(|layout| layout.is_1zst())
190                        {
191                            break;
192                        }
193
194                        let diag = if arg.span.contains(null_span) {
195                            InvalidNullArgumentsDiag::NullPtrInline { null_span }
196                        } else {
197                            InvalidNullArgumentsDiag::NullPtrThroughBinding { null_span }
198                        };
199
200                        cx.emit_span_lint(INVALID_NULL_ARGUMENTS, expr.span, diag)
201                    }
202                }
203            }
204
205            // Catching:
206            // (fn_ptr as *<const/mut> <ty>).is_null()
207            ExprKind::MethodCall(_, receiver, _, _)
208                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
209                    && matches!(
210                        cx.tcx.get_diagnostic_name(def_id),
211                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
212                    )
213                    && let Some(diag) = useless_check(cx, receiver) =>
214            {
215                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
216            }
217
218            ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
219                let to_check: &Expr<'_>;
220                let diag: UselessPtrNullChecksDiag<'_>;
221                if let Some(ddiag) = useless_check(cx, left) {
222                    to_check = right;
223                    diag = ddiag;
224                } else if let Some(ddiag) = useless_check(cx, right) {
225                    to_check = left;
226                    diag = ddiag;
227                } else {
228                    return;
229                }
230
231                match to_check.kind {
232                    // Catching:
233                    // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
234                    ExprKind::Cast(cast_expr, _)
235                        if let ExprKind::Lit(spanned) = cast_expr.kind
236                            && let LitKind::Int(v, _) = spanned.node
237                            && v == 0 =>
238                    {
239                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
240                    }
241
242                    // Catching:
243                    // (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
244                    ExprKind::Call(path, [])
245                        if let ExprKind::Path(ref qpath) = path.kind
246                            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
247                            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
248                            && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
249                    {
250                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
251                    }
252
253                    _ => {}
254                }
255            }
256            _ => {}
257        }
258    }
259}