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_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 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 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 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 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}