Skip to content

Commit 75efc14

Browse files
committed
Auto merge of rust-lang#7023 - boxdot:invalid-null-usage-v2, r=camsteffen
Invalid null usage v2 This is continuation of rust-lang#6192 after inactivity. I plan to move paths into the compiler as diagnostic items after this is merged. fixes rust-lang#1703 changelog: none
2 parents c40fa00 + 4f7fc11 commit 75efc14

File tree

8 files changed

+353
-15
lines changed

8 files changed

+353
-15
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,7 @@ Released 2018-09-13
22672267
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
22682268
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
22692269
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
2270+
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
22702271
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
22712272
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
22722273
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
903903
pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
904904
precedence::PRECEDENCE,
905905
ptr::CMP_NULL,
906+
ptr::INVALID_NULL_PTR_USAGE,
906907
ptr::MUT_FROM_REF,
907908
ptr::PTR_ARG,
908909
ptr_eq::PTR_EQ,
@@ -1672,6 +1673,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16721673
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
16731674
LintId::of(precedence::PRECEDENCE),
16741675
LintId::of(ptr::CMP_NULL),
1676+
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
16751677
LintId::of(ptr::MUT_FROM_REF),
16761678
LintId::of(ptr::PTR_ARG),
16771679
LintId::of(ptr_eq::PTR_EQ),
@@ -2011,6 +2013,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
20112013
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
20122014
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
20132015
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
2016+
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
20142017
LintId::of(ptr::MUT_FROM_REF),
20152018
LintId::of(ranges::REVERSED_EMPTY_RANGES),
20162019
LintId::of(regex::INVALID_REGEX),

clippy_lints/src/ptr.rs

+84-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
44
use clippy_utils::ptr::get_spans;
55
use clippy_utils::source::snippet_opt;
66
use clippy_utils::ty::{is_type_diagnostic_item, match_type, walk_ptrs_hir_ty};
7-
use clippy_utils::{is_allowed, match_qpath, paths};
7+
use clippy_utils::{is_allowed, match_def_path, paths};
88
use if_chain::if_chain;
99
use rustc_errors::Applicability;
1010
use rustc_hir::{
@@ -15,6 +15,7 @@ use rustc_lint::{LateContext, LateLintPass};
1515
use rustc_middle::ty;
1616
use rustc_session::{declare_lint_pass, declare_tool_lint};
1717
use rustc_span::source_map::Span;
18+
use rustc_span::symbol::Symbol;
1819
use rustc_span::{sym, MultiSpan};
1920
use std::borrow::Cow;
2021

@@ -94,7 +95,7 @@ declare_clippy_lint! {
9495
/// ```
9596
pub CMP_NULL,
9697
style,
97-
"comparing a pointer to a null pointer, suggesting to use `.is_null()` instead."
98+
"comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
9899
}
99100

100101
declare_clippy_lint! {
@@ -119,7 +120,28 @@ declare_clippy_lint! {
119120
"fns that create mutable refs from immutable ref args"
120121
}
121122

122-
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF]);
123+
declare_clippy_lint! {
124+
/// **What it does:** This lint checks for invalid usages of `ptr::null`.
125+
///
126+
/// **Why is this bad?** This causes undefined behavior.
127+
///
128+
/// **Known problems:** None.
129+
///
130+
/// **Example:**
131+
/// ```ignore
132+
/// // Bad. Undefined behavior
133+
/// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
134+
/// ```
135+
///
136+
/// // Good
137+
/// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
138+
/// ```
139+
pub INVALID_NULL_PTR_USAGE,
140+
correctness,
141+
"invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
142+
}
143+
144+
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
123145

124146
impl<'tcx> LateLintPass<'tcx> for Ptr {
125147
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
@@ -153,14 +175,63 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
153175

154176
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
155177
if let ExprKind::Binary(ref op, l, r) = expr.kind {
156-
if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(l) || is_null_path(r)) {
178+
if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
157179
span_lint(
158180
cx,
159181
CMP_NULL,
160182
expr.span,
161183
"comparing with null is better expressed by the `.is_null()` method",
162184
);
163185
}
186+
} else {
187+
check_invalid_ptr_usage(cx, expr);
188+
}
189+
}
190+
}
191+
192+
fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
193+
// (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
194+
const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
195+
(&paths::SLICE_FROM_RAW_PARTS, &[0]),
196+
(&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
197+
(&paths::PTR_COPY, &[0, 1]),
198+
(&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
199+
(&paths::PTR_READ, &[0]),
200+
(&paths::PTR_READ_UNALIGNED, &[0]),
201+
(&paths::PTR_READ_VOLATILE, &[0]),
202+
(&paths::PTR_REPLACE, &[0]),
203+
(&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
204+
(&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
205+
(&paths::PTR_SWAP, &[0, 1]),
206+
(&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
207+
(&paths::PTR_WRITE, &[0]),
208+
(&paths::PTR_WRITE_UNALIGNED, &[0]),
209+
(&paths::PTR_WRITE_VOLATILE, &[0]),
210+
(&paths::PTR_WRITE_BYTES, &[0]),
211+
];
212+
213+
if_chain! {
214+
if let ExprKind::Call(ref fun, ref args) = expr.kind;
215+
if let ExprKind::Path(ref qpath) = fun.kind;
216+
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
217+
let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
218+
if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
219+
.iter()
220+
.find(|&&(fn_path, _)| fn_path == fun_def_path);
221+
then {
222+
for &arg_idx in arg_indices {
223+
if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
224+
span_lint_and_sugg(
225+
cx,
226+
INVALID_NULL_PTR_USAGE,
227+
arg.span,
228+
"pointer must be non-null",
229+
"change this to",
230+
"core::ptr::NonNull::dangling().as_ptr()".to_string(),
231+
Applicability::MachineApplicable,
232+
);
233+
}
234+
}
164235
}
165236
}
166237
}
@@ -345,13 +416,15 @@ fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability,
345416
}
346417
}
347418

348-
fn is_null_path(expr: &Expr<'_>) -> bool {
349-
if let ExprKind::Call(pathexp, args) = expr.kind {
350-
if args.is_empty() {
351-
if let ExprKind::Path(ref path) = pathexp.kind {
352-
return match_qpath(path, &paths::PTR_NULL) || match_qpath(path, &paths::PTR_NULL_MUT);
353-
}
419+
fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
420+
if_chain! {
421+
if let ExprKind::Call(path, []) = expr.kind;
422+
if let ExprKind::Path(ref qpath) = path.kind;
423+
if let Some(fn_def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
424+
then {
425+
match_def_path(cx, fn_def_id, &paths::PTR_NULL) || match_def_path(cx, fn_def_id, &paths::PTR_NULL_MUT)
426+
} else {
427+
false
354428
}
355429
}
356-
false
357430
}

clippy_lints/src/size_of_in_element_count.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool)
6565

6666
fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> {
6767
const FUNCTIONS: [&[&str]; 8] = [
68-
&paths::COPY_NONOVERLAPPING,
69-
&paths::COPY,
68+
&paths::PTR_COPY_NONOVERLAPPING,
69+
&paths::PTR_COPY,
7070
&paths::WRITE_BYTES,
7171
&paths::PTR_SWAP_NONOVERLAPPING,
7272
&paths::PTR_SLICE_FROM_RAW_PARTS,

clippy_utils/src/paths.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeS
1818
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
1919
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
2020
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
21-
pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
22-
pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
2321
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
2422
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
2523
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
@@ -100,12 +98,23 @@ pub const PERMISSIONS_FROM_MODE: [&str; 7] = ["std", "sys", "unix", "ext", "fs",
10098
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
10199
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
102100
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
101+
pub const PTR_COPY: [&str; 4] = ["core", "intrinsics", "", "copy"];
102+
pub const PTR_COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
103103
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
104104
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
105105
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
106106
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
107107
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
108108
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
109+
pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
110+
pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
111+
pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
112+
pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
113+
pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
114+
pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
115+
pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
116+
pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
117+
pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
109118
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
110119
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
111120
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];

tests/ui/invalid_null_ptr_usage.fixed

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
unsafe {
5+
let _slice: &[usize] = std::slice::from_raw_parts(core::ptr::NonNull::dangling().as_ptr(), 0);
6+
let _slice: &[usize] = std::slice::from_raw_parts(core::ptr::NonNull::dangling().as_ptr(), 0);
7+
8+
let _slice: &[usize] = std::slice::from_raw_parts_mut(core::ptr::NonNull::dangling().as_ptr(), 0);
9+
10+
std::ptr::copy::<usize>(core::ptr::NonNull::dangling().as_ptr(), std::ptr::NonNull::dangling().as_ptr(), 0);
11+
std::ptr::copy::<usize>(std::ptr::NonNull::dangling().as_ptr(), core::ptr::NonNull::dangling().as_ptr(), 0);
12+
13+
std::ptr::copy_nonoverlapping::<usize>(core::ptr::NonNull::dangling().as_ptr(), std::ptr::NonNull::dangling().as_ptr(), 0);
14+
std::ptr::copy_nonoverlapping::<usize>(std::ptr::NonNull::dangling().as_ptr(), core::ptr::NonNull::dangling().as_ptr(), 0);
15+
16+
struct A; // zero sized struct
17+
assert_eq!(std::mem::size_of::<A>(), 0);
18+
19+
let _a: A = std::ptr::read(core::ptr::NonNull::dangling().as_ptr());
20+
let _a: A = std::ptr::read(core::ptr::NonNull::dangling().as_ptr());
21+
22+
let _a: A = std::ptr::read_unaligned(core::ptr::NonNull::dangling().as_ptr());
23+
let _a: A = std::ptr::read_unaligned(core::ptr::NonNull::dangling().as_ptr());
24+
25+
let _a: A = std::ptr::read_volatile(core::ptr::NonNull::dangling().as_ptr());
26+
let _a: A = std::ptr::read_volatile(core::ptr::NonNull::dangling().as_ptr());
27+
28+
let _a: A = std::ptr::replace(core::ptr::NonNull::dangling().as_ptr(), A);
29+
30+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts(core::ptr::NonNull::dangling().as_ptr(), 0);
31+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts(core::ptr::NonNull::dangling().as_ptr(), 0);
32+
33+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts_mut(core::ptr::NonNull::dangling().as_ptr(), 0);
34+
35+
std::ptr::swap::<A>(core::ptr::NonNull::dangling().as_ptr(), &mut A);
36+
std::ptr::swap::<A>(&mut A, core::ptr::NonNull::dangling().as_ptr());
37+
38+
std::ptr::swap_nonoverlapping::<A>(core::ptr::NonNull::dangling().as_ptr(), &mut A, 0);
39+
std::ptr::swap_nonoverlapping::<A>(&mut A, core::ptr::NonNull::dangling().as_ptr(), 0);
40+
41+
std::ptr::write(core::ptr::NonNull::dangling().as_ptr(), A);
42+
43+
std::ptr::write_unaligned(core::ptr::NonNull::dangling().as_ptr(), A);
44+
45+
std::ptr::write_volatile(core::ptr::NonNull::dangling().as_ptr(), A);
46+
47+
std::ptr::write_bytes::<usize>(core::ptr::NonNull::dangling().as_ptr(), 42, 0);
48+
}
49+
}

tests/ui/invalid_null_ptr_usage.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
unsafe {
5+
let _slice: &[usize] = std::slice::from_raw_parts(std::ptr::null(), 0);
6+
let _slice: &[usize] = std::slice::from_raw_parts(std::ptr::null_mut(), 0);
7+
8+
let _slice: &[usize] = std::slice::from_raw_parts_mut(std::ptr::null_mut(), 0);
9+
10+
std::ptr::copy::<usize>(std::ptr::null(), std::ptr::NonNull::dangling().as_ptr(), 0);
11+
std::ptr::copy::<usize>(std::ptr::NonNull::dangling().as_ptr(), std::ptr::null_mut(), 0);
12+
13+
std::ptr::copy_nonoverlapping::<usize>(std::ptr::null(), std::ptr::NonNull::dangling().as_ptr(), 0);
14+
std::ptr::copy_nonoverlapping::<usize>(std::ptr::NonNull::dangling().as_ptr(), std::ptr::null_mut(), 0);
15+
16+
struct A; // zero sized struct
17+
assert_eq!(std::mem::size_of::<A>(), 0);
18+
19+
let _a: A = std::ptr::read(std::ptr::null());
20+
let _a: A = std::ptr::read(std::ptr::null_mut());
21+
22+
let _a: A = std::ptr::read_unaligned(std::ptr::null());
23+
let _a: A = std::ptr::read_unaligned(std::ptr::null_mut());
24+
25+
let _a: A = std::ptr::read_volatile(std::ptr::null());
26+
let _a: A = std::ptr::read_volatile(std::ptr::null_mut());
27+
28+
let _a: A = std::ptr::replace(std::ptr::null_mut(), A);
29+
30+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
31+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts(std::ptr::null_mut(), 0);
32+
33+
let _slice: *const [usize] = std::ptr::slice_from_raw_parts_mut(std::ptr::null_mut(), 0);
34+
35+
std::ptr::swap::<A>(std::ptr::null_mut(), &mut A);
36+
std::ptr::swap::<A>(&mut A, std::ptr::null_mut());
37+
38+
std::ptr::swap_nonoverlapping::<A>(std::ptr::null_mut(), &mut A, 0);
39+
std::ptr::swap_nonoverlapping::<A>(&mut A, std::ptr::null_mut(), 0);
40+
41+
std::ptr::write(std::ptr::null_mut(), A);
42+
43+
std::ptr::write_unaligned(std::ptr::null_mut(), A);
44+
45+
std::ptr::write_volatile(std::ptr::null_mut(), A);
46+
47+
std::ptr::write_bytes::<usize>(std::ptr::null_mut(), 42, 0);
48+
}
49+
}

0 commit comments

Comments
 (0)