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