Skip to content

Commit 05132ba

Browse files
implement nonnull_unchecked_on_box_ptr lint
1 parent 9116cc8 commit 05132ba

11 files changed

Lines changed: 623 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7168,6 +7168,7 @@ Released 2018-09-13
71687168
[`non_std_lazy_statics`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics
71697169
[`non_zero_suggestions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_zero_suggestions
71707170
[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
7171+
[`nonnull_unchecked_on_box_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonnull_unchecked_on_box_ptr
71717172
[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
71727173
[`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces
71737174
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
985985
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
986986
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
987987
* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics)
988+
* [`nonnull_unchecked_on_box_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#nonnull_unchecked_on_box_ptr)
988989
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
989990
* [`or_fun_call`](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call)
990991
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)

clippy_config/src/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ define_Conf! {
840840
missing_const_for_fn,
841841
needless_borrow,
842842
non_std_lazy_statics,
843+
nonnull_unchecked_on_box_ptr,
843844
option_as_ref_deref,
844845
or_fun_call,
845846
ptr_as_ptr,

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
590590
crate::non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY_INFO,
591591
crate::non_std_lazy_statics::NON_STD_LAZY_STATICS_INFO,
592592
crate::non_zero_suggestions::NON_ZERO_SUGGESTIONS_INFO,
593+
crate::nonnull_unchecked_on_box_ptr::NONNULL_UNCHECKED_ON_BOX_PTR_INFO,
593594
crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO,
594595
crate::octal_escapes::OCTAL_ESCAPES_INFO,
595596
crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ mod non_octal_unix_permissions;
280280
mod non_send_fields_in_send_ty;
281281
mod non_std_lazy_statics;
282282
mod non_zero_suggestions;
283+
mod nonnull_unchecked_on_box_ptr;
283284
mod nonstandard_macro_braces;
284285
mod octal_escapes;
285286
mod only_used_in_recursion;
@@ -862,6 +863,7 @@ rustc_lint::late_lint_methods!(
862863
WithCapacityZero: with_capacity_zero::WithCapacityZero = with_capacity_zero::WithCapacityZero,
863864
RefPatterns: ref_patterns::RefPatterns = ref_patterns::RefPatterns,
864865
RedundantElse: redundant_else::RedundantElse = redundant_else::RedundantElse,
866+
NonnullUncheckedOnBoxPtr: nonnull_unchecked_on_box_ptr::NonnullUncheckedOnBoxPtr = nonnull_unchecked_on_box_ptr::NonnullUncheckedOnBoxPtr::new(conf),
865867
// add late passes here, used by `cargo dev new_lint`
866868
]]
867869
);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::res::{MaybeDef, MaybeQPath};
5+
use clippy_utils::source::snippet_with_context;
6+
use clippy_utils::sym;
7+
use clippy_utils::visitors::is_expr_unsafe;
8+
use rustc_errors::Applicability;
9+
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource};
10+
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_session::impl_lint_pass;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for unsafe usage of `NonNull::new_unchecked(Box::into_raw(x))`, and suggests calling `NonNull::from_mut(Box::leak(x))` instead.
16+
///
17+
/// ### Why is this bad?
18+
/// `NonNull::new_unchecked` is an unsafe function, which we don't need to call at all if we can instead use a mutable reference.
19+
///
20+
/// ### Example
21+
/// ```no_run
22+
/// use std::ptr::NonNull;
23+
/// let one = Box::new(1);
24+
/// let ptr = unsafe { NonNull::new_unchecked(Box::into_raw(one)) };
25+
/// ```
26+
/// Use instead:
27+
/// ```no_run
28+
/// use std::ptr::NonNull;
29+
/// let one = Box::new(1);
30+
/// let ptr = NonNull::from_mut(Box::leak(one));
31+
/// ```
32+
#[clippy::version = "1.98.0"]
33+
pub NONNULL_UNCHECKED_ON_BOX_PTR,
34+
complexity,
35+
"using `NonNull::new_unchecked` with `Box::into_raw`, while `NonNull::from_mut` with `Box::leak` can be used instead"
36+
}
37+
38+
impl_lint_pass!(NonnullUncheckedOnBoxPtr => [NONNULL_UNCHECKED_ON_BOX_PTR]);
39+
40+
pub struct NonnullUncheckedOnBoxPtr {
41+
msrv: Msrv,
42+
}
43+
44+
impl NonnullUncheckedOnBoxPtr {
45+
pub fn new(conf: &Conf) -> Self {
46+
Self { msrv: conf.msrv }
47+
}
48+
}
49+
50+
impl<'tcx> LateLintPass<'tcx> for NonnullUncheckedOnBoxPtr {
51+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
52+
if !expr.span.from_expansion()
53+
&& let ExprKind::Call(nonnull_new_unchecked, [arg]) = expr.kind
54+
&& let ExprKind::Call(box_into_raw, [arg]) = arg.kind
55+
&& nonnull_new_unchecked
56+
.ty_rel_def_if_named(cx, sym::new_unchecked)
57+
.opt_parent(cx)
58+
.opt_impl_ty(cx)
59+
.is_diag_item(cx, sym::NonNull)
60+
&& box_into_raw
61+
.ty_rel_def_if_named(cx, sym::into_raw)
62+
.opt_parent(cx)
63+
.opt_impl_ty(cx)
64+
.is_lang_item(cx, LangItem::OwnedBox)
65+
&& self.msrv.meets(cx, msrvs::BOX_LEAK)
66+
{
67+
let ctxt = expr.span.ctxt();
68+
let span = match cx.tcx.parent_hir_node(expr.hir_id) {
69+
Node::Block(&Block {
70+
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
71+
span: unsafe_span,
72+
stmts,
73+
..
74+
}) if unsafe_span.ctxt() == ctxt && !is_expr_unsafe(cx, arg) && stmts.is_empty() => unsafe_span,
75+
_ => expr.span,
76+
};
77+
78+
span_lint_and_then(
79+
cx,
80+
NONNULL_UNCHECKED_ON_BOX_PTR,
81+
span,
82+
"use of `NonNull::new_unchecked` with `Box::into_raw`",
83+
|diag| {
84+
let mut app = Applicability::MachineApplicable;
85+
let arg_name = snippet_with_context(cx, arg.span, ctxt, "_", &mut app).0;
86+
87+
let sugg = if self.msrv.meets(cx, msrvs::NONNULL_FROM_MUT) {
88+
format!("NonNull::from_mut(Box::leak({arg_name}))")
89+
} else {
90+
format!("NonNull::from(Box::leak({arg_name}))")
91+
};
92+
93+
diag.span_suggestion(span, "try", sugg, app);
94+
},
95+
);
96+
}
97+
}
98+
}

clippy_utils/src/msrvs.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ msrv_aliases! {
2727
1,97,0 { ISOLATE_LOWEST_ONE }
2828
1,93,0 { VEC_DEQUE_POP_BACK_IF, VEC_DEQUE_POP_FRONT_IF }
2929
1,91,0 { DURATION_FROM_MINUTES_HOURS }
30+
1,89,0 { NONNULL_FROM_MUT }
3031
1,88,0 { LET_CHAINS, AS_CHUNKS }
3132
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
3233
1,86,0 { VEC_POP_IF }
@@ -80,7 +81,7 @@ msrv_aliases! {
8081
1,29,0 { ITER_FLATTEN }
8182
1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
8283
1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND, DURATION_FROM_NANOS_MICROS }
83-
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS }
84+
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS, BOX_LEAK }
8485
1,24,0 { IS_ASCII_DIGIT, PTR_NULL }
8586
1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
8687
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }

clippy_utils/src/sym.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ generate! {
363363
into_bytes,
364364
into_ok,
365365
into_owned,
366+
into_raw,
366367
intrinsics_unaligned_volatile_load,
367368
intrinsics_unaligned_volatile_store,
368369
io,
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#![warn(clippy::nonnull_unchecked_on_box_ptr)]
2+
3+
use std::ptr::NonNull;
4+
5+
macro_rules! identity {
6+
($x:expr) => {
7+
$x
8+
};
9+
}
10+
11+
macro_rules! weird {
12+
($x:expr) => {{
13+
let y = 1;
14+
$x
15+
}};
16+
}
17+
18+
macro_rules! from_macro {
19+
($x:expr) => {
20+
unsafe { NonNull::new_unchecked(Box::into_raw($x)) }
21+
};
22+
}
23+
24+
fn identity<T>(x: T) -> T {
25+
x
26+
}
27+
28+
unsafe fn unsafe_identity<T>(x: T) -> T {
29+
x
30+
}
31+
32+
fn lint() {
33+
fn basic() {
34+
let one = Box::new(1);
35+
let _ = NonNull::from_mut(Box::leak(one));
36+
37+
let one = Box::new(1);
38+
let _ = NonNull::from_mut(Box::leak(identity(one)));
39+
}
40+
41+
fn qualifiers() {
42+
let one = Box::new(1);
43+
let _ = NonNull::from_mut(Box::leak(one));
44+
45+
{
46+
use Box as Box2;
47+
use NonNull as NonNull2;
48+
let one = Box::new(1);
49+
let _ = NonNull::from_mut(Box::leak(one));
50+
}
51+
52+
{
53+
type Box2<T> = Box<T>;
54+
type NonNull2<T> = NonNull<T>;
55+
let one = Box::new(1);
56+
let _ = NonNull::from_mut(Box::leak(one));
57+
}
58+
}
59+
60+
fn macros() {
61+
let one = Box::new(1);
62+
let _ = NonNull::from_mut(Box::leak(one));
63+
64+
let one = Box::new(1);
65+
let _ = identity!(NonNull::from_mut(Box::leak(one)));
66+
67+
let one = Box::new(1);
68+
let _ = NonNull::from_mut(Box::leak(one));
69+
70+
let one = Box::new(1);
71+
let _ = NonNull::from_mut(Box::leak(one));
72+
73+
let one = Box::new(1);
74+
let _ = NonNull::from_mut(Box::leak(one));
75+
76+
let one = Box::new(1);
77+
let _ = NonNull::from_mut(Box::leak(weird!(one)));
78+
}
79+
80+
fn keep_unsafe_block() {
81+
let one = Box::new(1);
82+
let _ = unsafe {
83+
NonNull::from_mut(Box::leak(unsafe_identity(one)))
84+
//~^ nonnull_unchecked_on_box_ptr
85+
};
86+
87+
let one = Box::new(1);
88+
let _ = unsafe {
89+
identity(NonNull::from_mut(Box::leak(one)))
90+
//~^ nonnull_unchecked_on_box_ptr
91+
};
92+
93+
let one = Box::new(1);
94+
let _ = unsafe {
95+
unsafe_identity(NonNull::from_mut(Box::leak(one)))
96+
//~^ nonnull_unchecked_on_box_ptr
97+
};
98+
99+
let _ = unsafe {
100+
NonNull::from_mut(Box::leak(Box::new(std::num::NonZeroI32::new_unchecked(1))))
101+
//~^ nonnull_unchecked_on_box_ptr
102+
};
103+
104+
let _ = unsafe {
105+
let one = Box::new(1);
106+
NonNull::from_mut(Box::leak(one))
107+
//~^ nonnull_unchecked_on_box_ptr
108+
};
109+
}
110+
}
111+
112+
fn no_lint() {
113+
fn basic() {
114+
let one = Box::new(1);
115+
let _ = NonNull::from_mut(Box::leak(one));
116+
117+
let one = Box::new(1);
118+
let _ = unsafe { NonNull::new_unchecked(identity(Box::into_raw(one))) };
119+
}
120+
121+
// TODO?
122+
fn does_not_check_expr_init() {
123+
let one = Box::new(1);
124+
let leaked = Box::into_raw(one);
125+
let _ = unsafe { NonNull::new_unchecked(leaked) };
126+
127+
let one = Box::new(1);
128+
let leaked = Box::leak(one);
129+
let _ = NonNull::from_mut(leaked);
130+
}
131+
132+
fn macros() {
133+
let one = Box::new(1);
134+
let _ = from_macro!(one);
135+
}
136+
}
137+
138+
#[clippy::msrv = "1.25"]
139+
fn msrv_1_25() {
140+
let one = Box::new(1);
141+
let _ = unsafe { NonNull::new_unchecked(Box::into_raw(one)) };
142+
}
143+
144+
#[clippy::msrv = "1.26"]
145+
fn msrv_1_26() {
146+
let one = Box::new(1);
147+
let _ = NonNull::from(Box::leak(one));
148+
}
149+
150+
#[clippy::msrv = "1.89"]
151+
fn msrv_1_89() {
152+
let one = Box::new(1);
153+
let _ = NonNull::from_mut(Box::leak(one));
154+
}
155+
156+
fn main() {}

0 commit comments

Comments
 (0)