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_replace
164                    | sym::ptr_write
165                    | sym::ptr_write_bytes
166                    | sym::ptr_write_unaligned => (&[0], true),
167                    sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut => (&[0], false),
168                    sym::ptr_copy
169                    | sym::ptr_copy_nonoverlapping
170                    | sym::ptr_swap
171                    | sym::ptr_swap_nonoverlapping => (&[0, 1], true),
172                    _ => return,
173                };
174
175                for &arg_idx in arg_indices {
176                    if let Some(arg) = args.get(arg_idx)
177                        && let Some(null_span) = is_null_ptr(cx, arg)
178                        && let Some(ty) = cx.typeck_results().expr_ty_opt(arg)
179                        && let RawPtr(ty, _mutbl) = ty.kind()
180                    {
181                        // If ZST are fine, don't lint on them
182                        let typing_env = cx.typing_env();
183                        if are_zsts_allowed
184                            && cx
185                                .tcx
186                                .layout_of(typing_env.as_query_input(*ty))
187                                .is_ok_and(|layout| layout.is_1zst())
188                        {
189                            break;
190                        }
191
192                        let diag = if arg.span.contains(null_span) {
193                            InvalidNullArgumentsDiag::NullPtrInline { null_span }
194                        } else {
195                            InvalidNullArgumentsDiag::NullPtrThroughBinding { null_span }
196                        };
197
198                        cx.emit_span_lint(INVALID_NULL_ARGUMENTS, expr.span, diag)
199                    }
200                }
201            }
202
203            // Catching:
204            // (fn_ptr as *<const/mut> <ty>).is_null()
205            ExprKind::MethodCall(_, receiver, _, _)
206                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
207                    && matches!(
208                        cx.tcx.get_diagnostic_name(def_id),
209                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
210                    )
211                    && let Some(diag) = useless_check(cx, receiver) =>
212            {
213                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
214            }
215
216            ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
217                let to_check: &Expr<'_>;
218                let diag: UselessPtrNullChecksDiag<'_>;
219                if let Some(ddiag) = useless_check(cx, left) {
220                    to_check = right;
221                    diag = ddiag;
222                } else if let Some(ddiag) = useless_check(cx, right) {
223                    to_check = left;
224                    diag = ddiag;
225                } else {
226                    return;
227                }
228
229                match to_check.kind {
230                    // Catching:
231                    // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
232                    ExprKind::Cast(cast_expr, _)
233                        if let ExprKind::Lit(spanned) = cast_expr.kind
234                            && let LitKind::Int(v, _) = spanned.node
235                            && v == 0 =>
236                    {
237                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
238                    }
239
240                    // Catching:
241                    // (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
242                    ExprKind::Call(path, [])
243                        if let ExprKind::Path(ref qpath) = path.kind
244                            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
245                            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
246                            && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
247                    {
248                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
249                    }
250
251                    _ => {}
252                }
253            }
254            _ => {}
255        }
256    }
257}