11use rustc_ast:: LitKind ;
22use rustc_hir:: { BinOpKind , Expr , ExprKind , TyKind } ;
3+ use rustc_middle:: ty:: RawPtr ;
34use 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:: { InvalidNullArgumentsDiag , UselessPtrNullChecksDiag } ;
8+ use crate :: utils:: peel_casts;
79use crate :: { LateContext , LateLintPass , LintContext } ;
810
911declare_lint ! {
@@ -31,7 +33,30 @@ declare_lint! {
3133 "useless checking of non-null-typed pointer"
3234}
3335
34- declare_lint_pass ! ( PtrNullChecks => [ USELESS_PTR_NULL_CHECKS ] ) ;
36+ declare_lint ! {
37+ /// The `invalid_null_arguments` lint checks for invalid usage of null pointers in arguments.
38+ ///
39+ /// ### Example
40+ ///
41+ /// ```rust,compile_fail
42+ /// # use std::{slice, ptr};
43+ /// // Undefined behavior
44+ /// # let _slice: &[u8] =
45+ /// unsafe { slice::from_raw_parts(ptr::null(), 0) };
46+ /// ```
47+ ///
48+ /// {{produces}}
49+ ///
50+ /// ### Explanation
51+ ///
52+ /// Calling methods whos safety invariants requires non-null ptr with a null pointer
53+ /// is [Undefined Behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html)!
54+ INVALID_NULL_ARGUMENTS ,
55+ Deny ,
56+ "invalid null pointer in arguments"
57+ }
58+
59+ declare_lint_pass ! ( PtrNullChecks => [ USELESS_PTR_NULL_CHECKS , INVALID_NULL_ARGUMENTS ] ) ;
3560
3661/// This function checks if the expression is from a series of consecutive casts,
3762/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
@@ -85,6 +110,25 @@ fn useless_check<'a, 'tcx: 'a>(
85110 }
86111}
87112
113+ /// Checks if the given expression is a null pointer (modulo casting)
114+ fn is_null_ptr < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < Span > {
115+ let ( expr, _) = peel_casts ( cx, expr) ;
116+
117+ if let ExprKind :: Call ( path, [ ] ) = expr. kind
118+ && let ExprKind :: Path ( ref qpath) = path. kind
119+ && let Some ( def_id) = cx. qpath_res ( qpath, path. hir_id ) . opt_def_id ( )
120+ && let Some ( diag_item) = cx. tcx . get_diagnostic_name ( def_id)
121+ {
122+ ( diag_item == sym:: ptr_null || diag_item == sym:: ptr_null_mut) . then_some ( expr. span )
123+ } else if let ExprKind :: Lit ( spanned) = expr. kind
124+ && let LitKind :: Int ( v, _) = spanned. node
125+ {
126+ ( v == 0 ) . then_some ( expr. span )
127+ } else {
128+ None
129+ }
130+ }
131+
88132impl < ' tcx > LateLintPass < ' tcx > for PtrNullChecks {
89133 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
90134 match expr. kind {
@@ -102,6 +146,81 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
102146 cx. emit_span_lint ( USELESS_PTR_NULL_CHECKS , expr. span , diag)
103147 }
104148
149+ // Catching:
150+ // <path>(arg...) where `arg` is null-ptr and `path` is a fn that expect non-null-ptr
151+ ExprKind :: Call ( path, args)
152+ if let ExprKind :: Path ( ref qpath) = path. kind
153+ && let Some ( def_id) = cx. qpath_res ( qpath, path. hir_id ) . opt_def_id ( )
154+ && let Some ( diag_name) = cx. tcx . get_diagnostic_name ( def_id) =>
155+ {
156+ // `arg` positions where null would cause U.B.
157+ //
158+ // We should probably have a `rustc` attribute, but checking them is costly,
159+ // maybe if we checked for null ptr first, it would be acceptable?
160+ let arg_indices: & [ _ ] = match diag_name {
161+ sym:: ptr_read
162+ | sym:: ptr_read_unaligned
163+ | sym:: ptr_read_volatile
164+ | sym:: ptr_replace
165+ | sym:: ptr_write
166+ | sym:: ptr_write_bytes
167+ | sym:: ptr_write_unaligned
168+ | sym:: ptr_write_volatile
169+ | sym:: slice_from_raw_parts
170+ | sym:: slice_from_raw_parts_mut => & [ 0 ] ,
171+ sym:: ptr_copy
172+ | sym:: ptr_copy_nonoverlapping
173+ | sym:: ptr_swap
174+ | sym:: ptr_swap_nonoverlapping => & [ 0 , 1 ] ,
175+ _ => return ,
176+ } ;
177+
178+ // Are Zero-Sized Types fine with null ptr for this method.
179+ let zst_are_fine = match diag_name {
180+ sym:: ptr_read
181+ | sym:: ptr_read_unaligned
182+ | sym:: ptr_read_volatile
183+ | sym:: ptr_replace
184+ | sym:: ptr_write
185+ | sym:: ptr_write_bytes
186+ | sym:: ptr_write_unaligned
187+ | sym:: ptr_write_volatile
188+ | sym:: ptr_copy
189+ | sym:: ptr_copy_nonoverlapping
190+ | sym:: ptr_swap
191+ | sym:: ptr_swap_nonoverlapping => true ,
192+ sym:: slice_from_raw_parts | sym:: slice_from_raw_parts_mut => false ,
193+ _ => return ,
194+ } ;
195+
196+ for & arg_idx in arg_indices {
197+ if let Some ( arg) = args. get ( arg_idx)
198+ && let Some ( null_span) = is_null_ptr ( cx, arg)
199+ && let Some ( ty) = cx. typeck_results ( ) . expr_ty_opt ( arg)
200+ && let RawPtr ( ty, _mutbl) = ty. kind ( )
201+ {
202+ // If ZST are fine, don't lint on them
203+ let typing_env = cx. typing_env ( ) ;
204+ if zst_are_fine
205+ && cx
206+ . tcx
207+ . layout_of ( typing_env. as_query_input ( * ty) )
208+ . is_ok_and ( |layout| layout. is_1zst ( ) )
209+ {
210+ break ;
211+ }
212+
213+ let null_span = if arg. span != null_span { Some ( null_span) } else { None } ;
214+
215+ cx. emit_span_lint (
216+ INVALID_NULL_ARGUMENTS ,
217+ expr. span ,
218+ InvalidNullArgumentsDiag { null_span } ,
219+ )
220+ }
221+ }
222+ }
223+
105224 // Catching:
106225 // (fn_ptr as *<const/mut> <ty>).is_null()
107226 ExprKind :: MethodCall ( _, receiver, _, _)
0 commit comments