rustc_lint/
ptr_nulls.rs

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