Skip to content

Commit d5ef013

Browse files
implement nonnull_unchecked_on_box_ptr lint
1 parent ffd8256 commit d5ef013

11 files changed

Lines changed: 504 additions & 0 deletions

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
@@ -984,6 +984,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
984984
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
985985
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
986986
* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics)
987+
* [`nonnull_unchecked_on_box_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#nonnull_unchecked_on_box_ptr)
987988
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
988989
* [`or_fun_call`](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call)
989990
* [`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
@@ -839,6 +839,7 @@ define_Conf! {
839839
missing_const_for_fn,
840840
needless_borrow,
841841
non_std_lazy_statics,
842+
nonnull_unchecked_on_box_ptr,
842843
option_as_ref_deref,
843844
or_fun_call,
844845
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
ManualAssertEq: manual_assert_eq::ManualAssertEq = manual_assert_eq::ManualAssertEq,
863864
WithCapacityZero: with_capacity_zero::WithCapacityZero = with_capacity_zero::WithCapacityZero,
864865
RefPatterns: ref_patterns::RefPatterns = ref_patterns::RefPatterns,
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: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::res::{MaybeDef, MaybeResPath};
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, QPath, 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+
// Parse `NonNull::new_unchecked`
54+
&& let ExprKind::Call(nonnull_new_unchecked, [arg]) = expr.kind
55+
&& let ExprKind::Path(QPath::TypeRelative(nonnnull, new_unchecked)) = nonnull_new_unchecked.kind
56+
&& new_unchecked.ident.name == sym::new_unchecked
57+
&& nonnnull.basic_res().is_diag_item(cx, sym::NonNull)
58+
// Parse `Box::into_raw`
59+
&& let ExprKind::Call(box_into_raw, [arg]) = arg.kind
60+
&& let ExprKind::Path(QPath::TypeRelative(r#box, into_raw)) = box_into_raw.kind
61+
&& into_raw.ident.name == sym::into_raw
62+
&& r#box.basic_res().is_lang_item(cx, LangItem::OwnedBox)
63+
&& self.msrv.meets(cx, msrvs::NONNULL_FROM_MUT)
64+
{
65+
let ctxt = expr.span.ctxt();
66+
let span = match cx.tcx.parent_hir_node(expr.hir_id) {
67+
Node::Block(
68+
block @ &Block {
69+
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
70+
span: unsafe_span,
71+
..
72+
},
73+
) if unsafe_span.ctxt() == ctxt && !is_expr_unsafe(cx, arg) && block.stmts.is_empty() => unsafe_span,
74+
_ => expr.span,
75+
};
76+
77+
let mut app = Applicability::MachineApplicable;
78+
let arg_name = snippet_with_context(cx, arg.span, ctxt, "_", &mut app).0;
79+
80+
span_lint_and_sugg(
81+
cx,
82+
NONNULL_UNCHECKED_ON_BOX_PTR,
83+
span,
84+
"use of `NonNull::new_unchecked` with `Box::into_raw`",
85+
"try",
86+
format!("NonNull::from_mut(Box::leak({arg_name}))"),
87+
app,
88+
);
89+
}
90+
}
91+
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
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 }

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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
//~^ nonnull_unchecked_on_box_ptr
37+
38+
let one = Box::new(1);
39+
let _ = NonNull::from_mut(Box::leak(identity(one)));
40+
//~^ nonnull_unchecked_on_box_ptr
41+
}
42+
43+
fn qualifiers() {
44+
let one = Box::new(1);
45+
let _ = NonNull::from_mut(Box::leak(one));
46+
//~^ nonnull_unchecked_on_box_ptr
47+
48+
use Box as Box2;
49+
use NonNull as NonNull2;
50+
let one = Box::new(1);
51+
let _ = NonNull::from_mut(Box::leak(one));
52+
//~^ nonnull_unchecked_on_box_ptr
53+
}
54+
55+
fn macros() {
56+
let one = Box::new(1);
57+
let _ = NonNull::from_mut(Box::leak(one));
58+
//~^ nonnull_unchecked_on_box_ptr
59+
60+
let one = Box::new(1);
61+
let _ = identity!(NonNull::from_mut(Box::leak(one)));
62+
//~^ nonnull_unchecked_on_box_ptr
63+
64+
let one = Box::new(1);
65+
let _ = NonNull::from_mut(Box::leak(one));
66+
//~^ nonnull_unchecked_on_box_ptr
67+
68+
let one = Box::new(1);
69+
let _ = NonNull::from_mut(Box::leak(one));
70+
//~^ nonnull_unchecked_on_box_ptr
71+
72+
let one = Box::new(1);
73+
let _ = NonNull::from_mut(Box::leak(one));
74+
//~^ nonnull_unchecked_on_box_ptr
75+
76+
let one = Box::new(1);
77+
let _ = NonNull::from_mut(Box::leak(weird!(one)));
78+
//~^ nonnull_unchecked_on_box_ptr
79+
}
80+
81+
fn keep_unsafe_block() {
82+
let one = Box::new(1);
83+
let _ = unsafe {
84+
NonNull::from_mut(Box::leak(unsafe_identity(one)))
85+
//~^ nonnull_unchecked_on_box_ptr
86+
};
87+
88+
let one = Box::new(1);
89+
let _ = unsafe {
90+
identity(NonNull::from_mut(Box::leak(one)))
91+
//~^ nonnull_unchecked_on_box_ptr
92+
};
93+
94+
let one = Box::new(1);
95+
let _ = unsafe {
96+
unsafe_identity(NonNull::from_mut(Box::leak(one)))
97+
//~^ nonnull_unchecked_on_box_ptr
98+
};
99+
100+
let _ = unsafe {
101+
NonNull::from_mut(Box::leak(Box::new(std::num::NonZeroI32::new_unchecked(1))))
102+
//~^ nonnull_unchecked_on_box_ptr
103+
};
104+
105+
let _ = unsafe {
106+
let one = Box::new(1);
107+
NonNull::from_mut(Box::leak(one))
108+
//~^ nonnull_unchecked_on_box_ptr
109+
};
110+
}
111+
}
112+
113+
fn no_lint() {
114+
fn basic() {
115+
let one = Box::new(1);
116+
let _ = NonNull::from_mut(Box::leak(one));
117+
118+
let one = Box::new(1);
119+
let _ = unsafe { NonNull::new_unchecked(identity(Box::into_raw(one))) };
120+
}
121+
122+
// TODO?
123+
fn does_not_check_expr_init() {
124+
let one = Box::new(1);
125+
let leaked = Box::into_raw(one);
126+
let _ = unsafe { NonNull::new_unchecked(leaked) };
127+
128+
let one = Box::new(1);
129+
let leaked = Box::leak(one);
130+
let _ = NonNull::from_mut(leaked);
131+
}
132+
133+
fn macros() {
134+
let one = Box::new(1);
135+
let _ = from_macro!(one);
136+
}
137+
}
138+
139+
#[clippy::msrv = "1.88"]
140+
fn msrv_1_88() {
141+
let one = Box::new(1);
142+
let _ = unsafe { NonNull::new_unchecked(Box::into_raw(one)) };
143+
}
144+
145+
#[clippy::msrv = "1.89"]
146+
fn msrv_1_89() {
147+
let one = Box::new(1);
148+
let _ = NonNull::from_mut(Box::leak(one));
149+
//~^ nonnull_unchecked_on_box_ptr
150+
}
151+
152+
fn main() {}

0 commit comments

Comments
 (0)