Skip to content

Commit c0ee13c

Browse files
committed
Uplift clippy::invalid_null_ptr_usage as invalid_null_arguments
1 parent d69b76a commit c0ee13c

File tree

5 files changed

+481
-3
lines changed

5 files changed

+481
-3
lines changed

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be dir
456456
457457
lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable
458458
459+
lint_invalid_null_arguments = calling this function with a null pointer is Undefined Behavior, even if the result of the function is unused
460+
.note = null pointer originate from here
461+
.suggestion = consider using a dangling pointer instead
462+
459463
lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
460464
.label = casting happened here
461465

compiler/rustc_lint/src/lints.rs

+24
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,30 @@ pub(crate) enum UselessPtrNullChecksDiag<'a> {
609609
FnRet { fn_name: Ident },
610610
}
611611

612+
#[derive(LintDiagnostic)]
613+
#[diag(lint_invalid_null_arguments)]
614+
pub(crate) struct InvalidNullArgumentsDiag<'a> {
615+
#[note]
616+
pub null_span: Option<Span>,
617+
#[subdiagnostic]
618+
pub suggestion: InvalidNullArgumentsSuggestion<'a>,
619+
}
620+
621+
#[derive(Subdiagnostic)]
622+
pub(crate) enum InvalidNullArgumentsSuggestion<'a> {
623+
#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
624+
WithoutExplicitType {
625+
#[suggestion_part(code = "core::ptr::NonNull::dangling().as_ptr()")]
626+
arg_span: Span,
627+
},
628+
#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
629+
WithExplicitType {
630+
ty: Ty<'a>,
631+
#[suggestion_part(code = "core::ptr::NonNull::<{ty}>::dangling().as_ptr()")]
632+
arg_span: Span,
633+
},
634+
}
635+
612636
// for_loops_over_fallibles.rs
613637
#[derive(LintDiagnostic)]
614638
#[diag(lint_for_loops_over_fallibles)]

compiler/rustc_lint/src/ptr_nulls.rs

+113-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use rustc_ast::LitKind;
22
use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
3+
use rustc_middle::ty::RawPtr;
34
use rustc_session::{declare_lint, declare_lint_pass};
4-
use rustc_span::sym;
5+
use rustc_span::{Span, sym};
56

6-
use crate::lints::UselessPtrNullChecksDiag;
7+
use crate::lints::{
8+
InvalidNullArgumentsDiag, InvalidNullArgumentsSuggestion, UselessPtrNullChecksDiag,
9+
};
10+
use crate::utils::peel_casts;
711
use crate::{LateContext, LateLintPass, LintContext};
812

913
declare_lint! {
@@ -31,7 +35,30 @@ declare_lint! {
3135
"useless checking of non-null-typed pointer"
3236
}
3337

34-
declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]);
38+
declare_lint! {
39+
/// The `invalid_null_arguments` lint checks for invalid usage of null pointers in arguments.
40+
///
41+
/// ### Example
42+
///
43+
/// ```rust,compile_fail
44+
/// # use std::{slice, ptr};
45+
/// // Undefined behavior
46+
/// # let _slice: &[u8] =
47+
/// unsafe { slice::from_raw_parts(ptr::null(), 0) };
48+
/// ```
49+
///
50+
/// {{produces}}
51+
///
52+
/// ### Explanation
53+
///
54+
/// Calling methods whos safety invariants requires non-null ptr with a null pointer
55+
/// is Undefined Behavior!
56+
INVALID_NULL_ARGUMENTS,
57+
Deny,
58+
"invalid null pointer in arguments"
59+
}
60+
61+
declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS, INVALID_NULL_ARGUMENTS]);
3562

3663
/// This function checks if the expression is from a series of consecutive casts,
3764
/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
@@ -85,6 +112,25 @@ fn useless_check<'a, 'tcx: 'a>(
85112
}
86113
}
87114

115+
/// Checks if the given expression is a null pointer (modulo casting)
116+
fn is_null_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Span> {
117+
let (expr, _) = peel_casts(cx, expr);
118+
119+
if let ExprKind::Call(path, []) = expr.kind
120+
&& let ExprKind::Path(ref qpath) = path.kind
121+
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
122+
&& let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
123+
{
124+
(diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut).then_some(expr.span)
125+
} else if let ExprKind::Lit(spanned) = expr.kind
126+
&& let LitKind::Int(v, _) = spanned.node
127+
{
128+
(v == 0).then_some(expr.span)
129+
} else {
130+
None
131+
}
132+
}
133+
88134
impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
89135
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
90136
match expr.kind {
@@ -102,6 +148,70 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
102148
cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
103149
}
104150

151+
// Catching:
152+
// <path>(arg...) where `arg` is null-ptr and `path` is a fn that expect non-null-ptr
153+
ExprKind::Call(path, args)
154+
if let ExprKind::Path(ref qpath) = path.kind
155+
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
156+
&& let Some(diag_name) = cx.tcx.get_diagnostic_name(def_id) =>
157+
{
158+
// `arg` positions where null would cause U.B.
159+
//
160+
// We should probably have a `rustc` attribute, but checking them is costly,
161+
// maybe if we checked for null ptr first, it would be acceptable?
162+
let arg_indices: &[_] = match diag_name {
163+
sym::ptr_read
164+
| sym::ptr_read_unaligned
165+
| sym::ptr_read_volatile
166+
| sym::ptr_replace
167+
| sym::ptr_write
168+
| sym::ptr_write_bytes
169+
| sym::ptr_write_unaligned
170+
| sym::ptr_write_volatile
171+
| sym::slice_from_raw_parts
172+
| sym::slice_from_raw_parts_mut => &[0],
173+
sym::ptr_copy
174+
| sym::ptr_copy_nonoverlapping
175+
| sym::ptr_swap
176+
| sym::ptr_swap_nonoverlapping => &[0, 1],
177+
_ => return,
178+
};
179+
180+
for &arg_idx in arg_indices {
181+
if let Some(arg) = args.get(arg_idx)
182+
&& let Some(null_span) = is_null_ptr(cx, arg)
183+
&& let Some(ty) = cx.typeck_results().expr_ty_opt(arg)
184+
&& let RawPtr(ty, _mutbl) = ty.kind()
185+
{
186+
// ZST are fine, don't lint on them
187+
let typing_env = cx.typing_env();
188+
if cx
189+
.tcx
190+
.layout_of(typing_env.as_query_input(*ty))
191+
.is_ok_and(|layout| layout.is_1zst())
192+
{
193+
break;
194+
}
195+
196+
let arg_span = arg.span;
197+
198+
let suggestion = if let ExprKind::Cast(..) = arg.peel_blocks().kind {
199+
InvalidNullArgumentsSuggestion::WithExplicitType { ty: *ty, arg_span }
200+
} else {
201+
InvalidNullArgumentsSuggestion::WithoutExplicitType { arg_span }
202+
};
203+
204+
let null_span = if arg_span != null_span { Some(null_span) } else { None };
205+
206+
cx.emit_span_lint(
207+
INVALID_NULL_ARGUMENTS,
208+
expr.span,
209+
InvalidNullArgumentsDiag { null_span, suggestion },
210+
)
211+
}
212+
}
213+
}
214+
105215
// Catching:
106216
// (fn_ptr as *<const/mut> <ty>).is_null()
107217
ExprKind::MethodCall(_, receiver, _, _)

tests/ui/lint/invalid_null_args.rs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// check-fail
2+
// run-rustfix
3+
4+
use std::ptr;
5+
use std::mem;
6+
7+
unsafe fn null_ptr() {
8+
ptr::write(
9+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
10+
ptr::null_mut() as *mut u32,
11+
mem::transmute::<[u8; 4], _>([0, 0, 0, 255]),
12+
);
13+
14+
let _: &[usize] = std::slice::from_raw_parts(ptr::null(), 0);
15+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
16+
let _: &[usize] = std::slice::from_raw_parts(ptr::null_mut(), 0);
17+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
18+
let _: &[usize] = std::slice::from_raw_parts(0 as *mut _, 0);
19+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
20+
let _: &[usize] = std::slice::from_raw_parts(mem::transmute(0usize), 0);
21+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
22+
23+
let _: &[usize] = std::slice::from_raw_parts_mut(ptr::null_mut(), 0);
24+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
25+
26+
ptr::copy::<usize>(ptr::null(), ptr::NonNull::dangling().as_ptr(), 0);
27+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
28+
ptr::copy::<usize>(ptr::NonNull::dangling().as_ptr(), ptr::null_mut(), 0);
29+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
30+
31+
ptr::copy_nonoverlapping::<usize>(ptr::null(), ptr::NonNull::dangling().as_ptr(), 0);
32+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
33+
ptr::copy_nonoverlapping::<usize>(
34+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
35+
ptr::NonNull::dangling().as_ptr(),
36+
ptr::null_mut(),
37+
0
38+
);
39+
40+
#[derive(Copy, Clone)]
41+
struct A(usize);
42+
let mut v = A(200);
43+
44+
let _a: A = ptr::read(ptr::null());
45+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
46+
let _a: A = ptr::read(ptr::null_mut());
47+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
48+
49+
let _a: A = ptr::read_unaligned(ptr::null());
50+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
51+
let _a: A = ptr::read_unaligned(ptr::null_mut());
52+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
53+
54+
let _a: A = ptr::read_volatile(ptr::null());
55+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
56+
let _a: A = ptr::read_volatile(ptr::null_mut());
57+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
58+
59+
let _a: A = ptr::replace(ptr::null_mut(), v);
60+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
61+
62+
ptr::swap::<A>(ptr::null_mut(), &mut v);
63+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
64+
ptr::swap::<A>(&mut v, ptr::null_mut());
65+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
66+
67+
ptr::swap_nonoverlapping::<A>(ptr::null_mut(), &mut v, 0);
68+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
69+
ptr::swap_nonoverlapping::<A>(&mut v, ptr::null_mut(), 0);
70+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
71+
72+
ptr::write(ptr::null_mut(), v);
73+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
74+
75+
ptr::write_unaligned(ptr::null_mut(), v);
76+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
77+
78+
ptr::write_volatile(ptr::null_mut(), v);
79+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
80+
81+
ptr::write_bytes::<usize>(ptr::null_mut(), 42, 0);
82+
//~^ ERROR calling this function with a null pointer is Undefined Behavior
83+
}
84+
85+
unsafe fn zst() {
86+
struct A; // zero-sized type
87+
88+
ptr::read::<()>(ptr::null());
89+
ptr::read::<A>(ptr::null());
90+
91+
ptr::write(ptr::null_mut(), ());
92+
ptr::write(ptr::null_mut(), A);
93+
}
94+
95+
unsafe fn not_invalid() {
96+
// Simplified false-positive from std quicksort implementation
97+
98+
let mut a = ptr::null_mut();
99+
let mut b = ();
100+
101+
loop {
102+
if false {
103+
break;
104+
}
105+
106+
a = &raw mut b;
107+
}
108+
109+
ptr::write(a, ());
110+
}
111+
112+
fn main() {}

0 commit comments

Comments
 (0)