@@ -59,8 +59,36 @@ declare_clippy_lint! {
59
59
restriction,
60
60
"creating an unsafe block without explaining why it is safe"
61
61
}
62
+ declare_clippy_lint ! {
63
+ /// ### What it does
64
+ /// Checks for `// SAFETY: ` comments on safe code.
65
+ ///
66
+ /// ### Why is this bad?
67
+ /// Safe code has no safety requirements, so there is no need to
68
+ /// describe safety invariants.
69
+ ///
70
+ /// ### Example
71
+ /// ```rust
72
+ /// use std::ptr::NonNull;
73
+ /// let a = &mut 42;
74
+ ///
75
+ /// // SAFETY: references are guaranteed to be non-null.
76
+ /// let ptr = NonNull::new(a).unwrap();
77
+ /// ```
78
+ /// Use instead:
79
+ /// ```rust
80
+ /// use std::ptr::NonNull;
81
+ /// let a = &mut 42;
82
+ ///
83
+ /// let ptr = NonNull::new(a).unwrap();
84
+ /// ```
85
+ #[ clippy:: version = "1.66.0" ]
86
+ pub UNNECESSARY_SAFETY_COMMENT ,
87
+ restriction,
88
+ "creating an unsafe block without explaining why it is safe"
89
+ }
62
90
63
- declare_lint_pass ! ( UndocumentedUnsafeBlocks => [ UNDOCUMENTED_UNSAFE_BLOCKS ] ) ;
91
+ declare_lint_pass ! ( UndocumentedUnsafeBlocks => [ UNDOCUMENTED_UNSAFE_BLOCKS , UNNECESSARY_SAFETY_COMMENT ] ) ;
64
92
65
93
impl LateLintPass < ' _ > for UndocumentedUnsafeBlocks {
66
94
fn check_block ( & mut self , cx : & LateContext < ' _ > , block : & ' _ Block < ' _ > ) {
@@ -90,28 +118,95 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
90
118
}
91
119
92
120
fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) {
93
- if let hir:: ItemKind :: Impl ( imple) = item. kind
94
- && imple. unsafety == hir:: Unsafety :: Unsafe
95
- && !in_external_macro ( cx. tcx . sess , item. span )
96
- && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , item. hir_id ( ) )
97
- && !is_unsafe_from_proc_macro ( cx, item. span )
98
- && !item_has_safety_comment ( cx, item)
99
- {
121
+ if in_external_macro ( cx. tcx . sess , item. span ) {
122
+ return ;
123
+ }
124
+
125
+ let mk_spans = |pos : BytePos | {
100
126
let source_map = cx. tcx . sess . source_map ( ) ;
127
+ let span = Span :: new ( pos, pos, SyntaxContext :: root ( ) , None ) ;
128
+ let help_span = source_map. span_extend_to_next_char ( span, '\n' , true ) ;
101
129
let span = if source_map. is_multiline ( item. span ) {
102
130
source_map. span_until_char ( item. span , '\n' )
103
131
} else {
104
132
item. span
105
133
} ;
134
+ ( span, help_span)
135
+ } ;
106
136
107
- span_lint_and_help (
108
- cx,
109
- UNDOCUMENTED_UNSAFE_BLOCKS ,
110
- span,
111
- "unsafe impl missing a safety comment" ,
112
- None ,
113
- "consider adding a safety comment on the preceding line" ,
114
- ) ;
137
+ let item_has_safety_comment = item_has_safety_comment ( cx, item) ;
138
+ match ( & item. kind , item_has_safety_comment) {
139
+ ( hir:: ItemKind :: Impl ( impl_) , Some ( None ) ) if impl_. unsafety == hir:: Unsafety :: Unsafe => {
140
+ if !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , item. hir_id ( ) )
141
+ && !is_unsafe_from_proc_macro ( cx, item. span )
142
+ {
143
+ let source_map = cx. tcx . sess . source_map ( ) ;
144
+ let span = if source_map. is_multiline ( item. span ) {
145
+ source_map. span_until_char ( item. span , '\n' )
146
+ } else {
147
+ item. span
148
+ } ;
149
+
150
+ span_lint_and_help (
151
+ cx,
152
+ UNDOCUMENTED_UNSAFE_BLOCKS ,
153
+ span,
154
+ "unsafe impl missing a safety comment" ,
155
+ None ,
156
+ "consider adding a safety comment on the preceding line" ,
157
+ ) ;
158
+ }
159
+ } ,
160
+ ( hir:: ItemKind :: Impl ( impl_) , Some ( Some ( pos) ) ) if impl_. unsafety == hir:: Unsafety :: Normal => {
161
+ if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , item. hir_id ( ) ) {
162
+ let ( span, help_span) = mk_spans ( pos) ;
163
+
164
+ span_lint_and_help (
165
+ cx,
166
+ UNNECESSARY_SAFETY_COMMENT ,
167
+ span,
168
+ "impl has unnecessary safety comment" ,
169
+ Some ( help_span) ,
170
+ "consider removing the safety comment" ,
171
+ ) ;
172
+ }
173
+ } ,
174
+ ( hir:: ItemKind :: Impl ( _) , _) => { } ,
175
+ ( & hir:: ItemKind :: Const ( .., body) | & hir:: ItemKind :: Static ( .., body) , Some ( Some ( pos) ) ) => {
176
+ if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , body. hir_id ) {
177
+ let body = cx. tcx . hir ( ) . body ( body) ;
178
+ if !matches ! (
179
+ body. value. kind, hir:: ExprKind :: Block ( block, _)
180
+ if block. rules == BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided )
181
+ ) {
182
+ let ( span, help_span) = mk_spans ( pos) ;
183
+
184
+ span_lint_and_help (
185
+ cx,
186
+ UNNECESSARY_SAFETY_COMMENT ,
187
+ span,
188
+ & format ! ( "{} has unnecessary safety comment" , item. kind. descr( ) ) ,
189
+ Some ( help_span) ,
190
+ "consider removing the safety comment" ,
191
+ ) ;
192
+ }
193
+ }
194
+ } ,
195
+ ( _, Some ( Some ( pos) ) ) => {
196
+ if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , item. hir_id ( ) ) {
197
+ let ( span, help_span) = mk_spans ( pos) ;
198
+
199
+ span_lint_and_help (
200
+ cx,
201
+ UNNECESSARY_SAFETY_COMMENT ,
202
+ span,
203
+ & format ! ( "{} has unnecessary safety comment" , item. kind. descr( ) ) ,
204
+ Some ( help_span) ,
205
+ "consider removing the safety comment" ,
206
+ ) ;
207
+ }
208
+ } ,
209
+ _ => ( ) ,
115
210
}
116
211
}
117
212
}
@@ -170,28 +265,29 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
170
265
// won't work. This is to avoid dealing with where such a comment should be place relative to
171
266
// attributes and doc comments.
172
267
173
- span_from_macro_expansion_has_safety_comment ( cx, span) || span_in_body_has_safety_comment ( cx, span)
268
+ matches ! ( span_from_macro_expansion_has_safety_comment( cx, span) , Some ( Some ( _) ) )
269
+ || span_in_body_has_safety_comment ( cx, span)
174
270
}
175
271
176
272
/// Checks if the lines immediately preceding the item contain a safety comment.
177
273
#[ allow( clippy:: collapsible_match) ]
178
- fn item_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) -> bool {
179
- if span_from_macro_expansion_has_safety_comment ( cx, item. span ) {
180
- return true ;
274
+ fn item_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) -> Option < Option < BytePos > > {
275
+ if let res @ Some ( _ ) = span_from_macro_expansion_has_safety_comment ( cx, item. span ) {
276
+ return res ;
181
277
}
182
278
183
279
if item. span . ctxt ( ) == SyntaxContext :: root ( ) {
184
280
if let Some ( parent_node) = get_parent_node ( cx. tcx , item. hir_id ( ) ) {
185
281
let comment_start = match parent_node {
186
282
Node :: Crate ( parent_mod) => {
187
- comment_start_before_impl_in_mod ( cx, parent_mod, parent_mod. spans . inner_span , item)
283
+ comment_start_before_item_in_mod ( cx, parent_mod, parent_mod. spans . inner_span , item)
188
284
} ,
189
285
Node :: Item ( parent_item) => {
190
286
if let ItemKind :: Mod ( parent_mod) = & parent_item. kind {
191
- comment_start_before_impl_in_mod ( cx, parent_mod, parent_item. span , item)
287
+ comment_start_before_item_in_mod ( cx, parent_mod, parent_item. span , item)
192
288
} else {
193
289
// Doesn't support impls in this position. Pretend a comment was found.
194
- return true ;
290
+ return None ;
195
291
}
196
292
} ,
197
293
Node :: Stmt ( stmt) => {
@@ -200,17 +296,17 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> bool {
200
296
Node :: Block ( block) => walk_span_to_context ( block. span , SyntaxContext :: root ( ) ) . map ( Span :: lo) ,
201
297
_ => {
202
298
// Doesn't support impls in this position. Pretend a comment was found.
203
- return true ;
299
+ return None ;
204
300
} ,
205
301
}
206
302
} else {
207
303
// Problem getting the parent node. Pretend a comment was found.
208
- return true ;
304
+ return None ;
209
305
}
210
306
} ,
211
307
_ => {
212
308
// Doesn't support impls in this position. Pretend a comment was found.
213
- return true ;
309
+ return None ;
214
310
} ,
215
311
} ;
216
312
@@ -221,34 +317,38 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> bool {
221
317
&& Lrc :: ptr_eq ( & unsafe_line. sf , & comment_start_line. sf )
222
318
&& let Some ( src) = unsafe_line. sf . src . as_deref ( )
223
319
{
224
- unsafe_line. sf . lines ( |lines| {
225
- comment_start_line. line < unsafe_line. line && text_has_safety_comment (
226
- src,
227
- & lines[ comment_start_line. line + 1 ..=unsafe_line. line ] ,
228
- unsafe_line. sf . start_pos . to_usize ( ) ,
229
- )
230
- } )
320
+ Some ( unsafe_line. sf . lines ( |lines| {
321
+ if comment_start_line. line >= unsafe_line. line {
322
+ None
323
+ } else {
324
+ text_has_safety_comment (
325
+ src,
326
+ & lines[ comment_start_line. line + 1 ..=unsafe_line. line ] ,
327
+ unsafe_line. sf . start_pos . to_usize ( ) ,
328
+ )
329
+ }
330
+ } ) )
231
331
} else {
232
332
// Problem getting source text. Pretend a comment was found.
233
- true
333
+ None
234
334
}
235
335
} else {
236
336
// No parent node. Pretend a comment was found.
237
- true
337
+ None
238
338
}
239
339
} else {
240
- false
340
+ Some ( None )
241
341
}
242
342
}
243
343
244
- fn comment_start_before_impl_in_mod (
344
+ fn comment_start_before_item_in_mod (
245
345
cx : & LateContext < ' _ > ,
246
346
parent_mod : & hir:: Mod < ' _ > ,
247
347
parent_mod_span : Span ,
248
- imple : & hir:: Item < ' _ > ,
348
+ item : & hir:: Item < ' _ > ,
249
349
) -> Option < BytePos > {
250
350
parent_mod. item_ids . iter ( ) . enumerate ( ) . find_map ( |( idx, item_id) | {
251
- if * item_id == imple . item_id ( ) {
351
+ if * item_id == item . item_id ( ) {
252
352
if idx == 0 {
253
353
// mod A { /* comment */ unsafe impl T {} ... }
254
354
// ^------------------------------------------^ returns the start of this span
@@ -270,11 +370,11 @@ fn comment_start_before_impl_in_mod(
270
370
} )
271
371
}
272
372
273
- fn span_from_macro_expansion_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> bool {
373
+ fn span_from_macro_expansion_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> Option < Option < BytePos > > {
274
374
let source_map = cx. sess ( ) . source_map ( ) ;
275
375
let ctxt = span. ctxt ( ) ;
276
376
if ctxt == SyntaxContext :: root ( ) {
277
- false
377
+ None
278
378
} else {
279
379
// From a macro expansion. Get the text from the start of the macro declaration to start of the
280
380
// unsafe block.
@@ -285,16 +385,20 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
285
385
&& Lrc :: ptr_eq ( & unsafe_line. sf , & macro_line. sf )
286
386
&& let Some ( src) = unsafe_line. sf . src . as_deref ( )
287
387
{
288
- unsafe_line. sf . lines ( |lines| {
289
- macro_line. line < unsafe_line. line && text_has_safety_comment (
290
- src,
291
- & lines[ macro_line. line + 1 ..=unsafe_line. line ] ,
292
- unsafe_line. sf . start_pos . to_usize ( ) ,
293
- )
294
- } )
388
+ Some ( unsafe_line. sf . lines ( |lines| {
389
+ if macro_line. line < unsafe_line. line {
390
+ text_has_safety_comment (
391
+ src,
392
+ & lines[ macro_line. line + 1 ..=unsafe_line. line ] ,
393
+ unsafe_line. sf . start_pos . to_usize ( ) ,
394
+ )
395
+ } else {
396
+ None
397
+ }
398
+ } ) )
295
399
} else {
296
400
// Problem getting source text. Pretend a comment was found.
297
- true
401
+ None
298
402
}
299
403
}
300
404
}
@@ -333,7 +437,7 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
333
437
src,
334
438
& lines[ body_line. line + 1 ..=unsafe_line. line ] ,
335
439
unsafe_line. sf . start_pos . to_usize ( ) ,
336
- )
440
+ ) . is_some ( )
337
441
} )
338
442
} else {
339
443
// Problem getting source text. Pretend a comment was found.
@@ -345,30 +449,34 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
345
449
}
346
450
347
451
/// Checks if the given text has a safety comment for the immediately proceeding line.
348
- fn text_has_safety_comment ( src : & str , line_starts : & [ BytePos ] , offset : usize ) -> bool {
452
+ fn text_has_safety_comment ( src : & str , line_starts : & [ BytePos ] , offset : usize ) -> Option < BytePos > {
349
453
let mut lines = line_starts
350
454
. array_windows :: < 2 > ( )
351
455
. rev ( )
352
456
. map_while ( |[ start, end] | {
353
457
let start = start. to_usize ( ) - offset;
354
458
let end = end. to_usize ( ) - offset;
355
- src. get ( start..end) . map ( |text| ( start, text. trim_start ( ) ) )
459
+ let text = src. get ( start..end) ?;
460
+ let trimmed = text. trim_start ( ) ;
461
+ Some ( ( start + ( text. len ( ) - trimmed. len ( ) ) , trimmed) )
356
462
} )
357
463
. filter ( |( _, text) | !text. is_empty ( ) ) ;
358
464
359
465
let Some ( ( line_start, line) ) = lines. next ( ) else {
360
- return false ;
466
+ return None ;
361
467
} ;
362
468
// Check for a sequence of line comments.
363
469
if line. starts_with ( "//" ) {
364
- let mut line = line;
470
+ let ( mut line, mut line_start ) = ( line, line_start ) ;
365
471
loop {
366
472
if line. to_ascii_uppercase ( ) . contains ( "SAFETY:" ) {
367
- return true ;
473
+ return Some ( BytePos (
474
+ u32:: try_from ( line_start) . unwrap ( ) + u32:: try_from ( offset) . unwrap ( ) ,
475
+ ) ) ;
368
476
}
369
477
match lines. next ( ) {
370
- Some ( ( _ , x) ) if x. starts_with ( "//" ) => line = x ,
371
- _ => return false ,
478
+ Some ( ( s , x) ) if x. starts_with ( "//" ) => ( line, line_start ) = ( x , s ) ,
479
+ _ => return None ,
372
480
}
373
481
}
374
482
}
@@ -377,16 +485,19 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
377
485
let ( mut line_start, mut line) = ( line_start, line) ;
378
486
loop {
379
487
if line. starts_with ( "/*" ) {
380
- let src = src[ line_start..line_starts. last ( ) . unwrap ( ) . to_usize ( ) - offset] . trim_start ( ) ;
488
+ let src = & src[ line_start..line_starts. last ( ) . unwrap ( ) . to_usize ( ) - offset] ;
381
489
let mut tokens = tokenize ( src) ;
382
- return src[ ..tokens. next ( ) . unwrap ( ) . len as usize ]
490
+ return ( src[ ..tokens. next ( ) . unwrap ( ) . len as usize ]
383
491
. to_ascii_uppercase ( )
384
492
. contains ( "SAFETY:" )
385
- && tokens. all ( |t| t. kind == TokenKind :: Whitespace ) ;
493
+ && tokens. all ( |t| t. kind == TokenKind :: Whitespace ) )
494
+ . then_some ( BytePos (
495
+ u32:: try_from ( line_start) . unwrap ( ) + u32:: try_from ( offset) . unwrap ( ) ,
496
+ ) ) ;
386
497
}
387
498
match lines. next ( ) {
388
499
Some ( x) => ( line_start, line) = x,
389
- None => return false ,
500
+ None => return None ,
390
501
}
391
502
}
392
503
}
0 commit comments