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:: {
8
+ InvalidNullArgumentsDiag , InvalidNullArgumentsSuggestion , UselessPtrNullChecksDiag ,
9
+ } ;
10
+ use crate :: utils:: peel_casts;
7
11
use crate :: { LateContext , LateLintPass , LintContext } ;
8
12
9
13
declare_lint ! {
@@ -31,7 +35,30 @@ declare_lint! {
31
35
"useless checking of non-null-typed pointer"
32
36
}
33
37
34
- declare_lint_pass ! ( PtrNullChecks => [ USELESS_PTR_NULL_CHECKS ] ) ;
38
+ declare_lint ! {
39
+ /// The `invalid_null_arguments` lint checks for invalid usage of null pointers in arguments.
40
+ ///
41
+ /// ### Example
42
+ ///
43
+ /// ```rust,compile_fail
44
+ /// # use std::{slice, ptr};
45
+ /// // Undefined behavior
46
+ /// # let _slice: &[u8] =
47
+ /// unsafe { slice::from_raw_parts(ptr::null(), 0) };
48
+ /// ```
49
+ ///
50
+ /// {{produces}}
51
+ ///
52
+ /// ### Explanation
53
+ ///
54
+ /// Calling methods whos safety invariants requires non-null ptr with a null pointer
55
+ /// is Undefined Behavior!
56
+ INVALID_NULL_ARGUMENTS ,
57
+ Deny ,
58
+ "invalid null pointer in arguments"
59
+ }
60
+
61
+ declare_lint_pass ! ( PtrNullChecks => [ USELESS_PTR_NULL_CHECKS , INVALID_NULL_ARGUMENTS ] ) ;
35
62
36
63
/// This function checks if the expression is from a series of consecutive casts,
37
64
/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
@@ -85,6 +112,25 @@ fn useless_check<'a, 'tcx: 'a>(
85
112
}
86
113
}
87
114
115
+ /// Checks if the given expression is a null pointer (modulo casting)
116
+ fn is_null_ptr < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < Span > {
117
+ let ( expr, _) = peel_casts ( cx, expr) ;
118
+
119
+ if let ExprKind :: Call ( path, [ ] ) = expr. kind
120
+ && let ExprKind :: Path ( ref qpath) = path. kind
121
+ && let Some ( def_id) = cx. qpath_res ( qpath, path. hir_id ) . opt_def_id ( )
122
+ && let Some ( diag_item) = cx. tcx . get_diagnostic_name ( def_id)
123
+ {
124
+ ( diag_item == sym:: ptr_null || diag_item == sym:: ptr_null_mut) . then_some ( expr. span )
125
+ } else if let ExprKind :: Lit ( spanned) = expr. kind
126
+ && let LitKind :: Int ( v, _) = spanned. node
127
+ {
128
+ ( v == 0 ) . then_some ( expr. span )
129
+ } else {
130
+ None
131
+ }
132
+ }
133
+
88
134
impl < ' tcx > LateLintPass < ' tcx > for PtrNullChecks {
89
135
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
90
136
match expr. kind {
@@ -102,6 +148,70 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
102
148
cx. emit_span_lint ( USELESS_PTR_NULL_CHECKS , expr. span , diag)
103
149
}
104
150
151
+ // Catching:
152
+ // <path>(arg...) where `arg` is null-ptr and `path` is a fn that expect non-null-ptr
153
+ ExprKind :: Call ( path, args)
154
+ if let ExprKind :: Path ( ref qpath) = path. kind
155
+ && let Some ( def_id) = cx. qpath_res ( qpath, path. hir_id ) . opt_def_id ( )
156
+ && let Some ( diag_name) = cx. tcx . get_diagnostic_name ( def_id) =>
157
+ {
158
+ // `arg` positions where null would cause U.B.
159
+ //
160
+ // We should probably have a `rustc` attribute, but checking them is costly,
161
+ // maybe if we checked for null ptr first, it would be acceptable?
162
+ let arg_indices: & [ _ ] = match diag_name {
163
+ sym:: ptr_read
164
+ | sym:: ptr_read_unaligned
165
+ | sym:: ptr_read_volatile
166
+ | sym:: ptr_replace
167
+ | sym:: ptr_write
168
+ | sym:: ptr_write_bytes
169
+ | sym:: ptr_write_unaligned
170
+ | sym:: ptr_write_volatile
171
+ | sym:: slice_from_raw_parts
172
+ | sym:: slice_from_raw_parts_mut => & [ 0 ] ,
173
+ sym:: ptr_copy
174
+ | sym:: ptr_copy_nonoverlapping
175
+ | sym:: ptr_swap
176
+ | sym:: ptr_swap_nonoverlapping => & [ 0 , 1 ] ,
177
+ _ => return ,
178
+ } ;
179
+
180
+ for & arg_idx in arg_indices {
181
+ if let Some ( arg) = args. get ( arg_idx)
182
+ && let Some ( null_span) = is_null_ptr ( cx, arg)
183
+ && let Some ( ty) = cx. typeck_results ( ) . expr_ty_opt ( arg)
184
+ && let RawPtr ( ty, _mutbl) = ty. kind ( )
185
+ {
186
+ // ZST are fine, don't lint on them
187
+ let typing_env = cx. typing_env ( ) ;
188
+ if cx
189
+ . tcx
190
+ . layout_of ( typing_env. as_query_input ( * ty) )
191
+ . is_ok_and ( |layout| layout. is_1zst ( ) )
192
+ {
193
+ break ;
194
+ }
195
+
196
+ let arg_span = arg. span ;
197
+
198
+ let suggestion = if let ExprKind :: Cast ( ..) = arg. peel_blocks ( ) . kind {
199
+ InvalidNullArgumentsSuggestion :: WithExplicitType { ty : * ty, arg_span }
200
+ } else {
201
+ InvalidNullArgumentsSuggestion :: WithoutExplicitType { arg_span }
202
+ } ;
203
+
204
+ let null_span = if arg_span != null_span { Some ( null_span) } else { None } ;
205
+
206
+ cx. emit_span_lint (
207
+ INVALID_NULL_ARGUMENTS ,
208
+ expr. span ,
209
+ InvalidNullArgumentsDiag { null_span, suggestion } ,
210
+ )
211
+ }
212
+ }
213
+ }
214
+
105
215
// Catching:
106
216
// (fn_ptr as *<const/mut> <ty>).is_null()
107
217
ExprKind :: MethodCall ( _, receiver, _, _)
0 commit comments