1
1
use rustc_ast:: LitKind ;
2
2
use rustc_hir:: { BinOpKind , Expr , ExprKind , TyKind } ;
3
+ use rustc_middle:: ty:: RawPtr ;
3
4
use rustc_session:: { declare_lint, declare_lint_pass} ;
4
- use rustc_span:: sym;
5
+ use rustc_span:: { Span , sym} ;
5
6
6
- use crate :: lints:: UselessPtrNullChecksDiag ;
7
+ use crate :: lints:: { InvalidNullArgumentsDiag , UselessPtrNullChecksDiag } ;
8
+ use crate :: utils:: peel_casts;
7
9
use crate :: { LateContext , LateLintPass , LintContext } ;
8
10
9
11
declare_lint ! {
@@ -31,7 +33,30 @@ declare_lint! {
31
33
"useless checking of non-null-typed pointer"
32
34
}
33
35
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 ] ) ;
35
60
36
61
/// This function checks if the expression is from a series of consecutive casts,
37
62
/// 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>(
85
110
}
86
111
}
87
112
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
+
88
132
impl < ' tcx > LateLintPass < ' tcx > for PtrNullChecks {
89
133
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
90
134
match expr. kind {
@@ -102,6 +146,81 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
102
146
cx. emit_span_lint ( USELESS_PTR_NULL_CHECKS , expr. span , diag)
103
147
}
104
148
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
+
105
224
// Catching:
106
225
// (fn_ptr as *<const/mut> <ty>).is_null()
107
226
ExprKind :: MethodCall ( _, receiver, _, _)
0 commit comments