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 USELESS_PTR_NULL_CHECKS,
32 Warn,
33 "useless checking of non-null-typed pointer"
34}
35
36declare_lint! {
37 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
61fn 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
113fn 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 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 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 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 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 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 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 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}