@@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
44use clippy_utils:: ptr:: get_spans;
55use clippy_utils:: source:: snippet_opt;
66use 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} ;
88use if_chain:: if_chain;
99use rustc_errors:: Applicability ;
1010use rustc_hir:: {
@@ -15,6 +15,7 @@ use rustc_lint::{LateContext, LateLintPass};
1515use rustc_middle:: ty;
1616use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1717use rustc_span:: source_map:: Span ;
18+ use rustc_span:: symbol:: Symbol ;
1819use rustc_span:: { sym, MultiSpan } ;
1920use 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
100101declare_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
124146impl < ' 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}
0 commit comments