1+ use std:: ops:: ControlFlow ;
2+
13use clippy_utils:: diagnostics:: span_lint_and_help;
24use clippy_utils:: source:: walk_span_to_context;
5+ use clippy_utils:: visitors:: { for_each_expr_with_closures, Descend } ;
36use clippy_utils:: { get_parent_node, is_lint_allowed} ;
7+ use hir:: HirId ;
48use rustc_data_structures:: sync:: Lrc ;
59use rustc_hir as hir;
610use rustc_hir:: { Block , BlockCheckMode , ItemKind , Node , UnsafeSource } ;
@@ -90,8 +94,8 @@ declare_clippy_lint! {
9094
9195declare_lint_pass ! ( UndocumentedUnsafeBlocks => [ UNDOCUMENTED_UNSAFE_BLOCKS , UNNECESSARY_SAFETY_COMMENT ] ) ;
9296
93- impl LateLintPass < ' _ > for UndocumentedUnsafeBlocks {
94- fn check_block ( & mut self , cx : & LateContext < ' _ > , block : & ' _ Block < ' _ > ) {
97+ impl < ' tcx > LateLintPass < ' tcx > for UndocumentedUnsafeBlocks {
98+ fn check_block ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx Block < ' tcx > ) {
9599 if block. rules == BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided )
96100 && !in_external_macro ( cx. tcx . sess , block. span )
97101 && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , block. hir_id )
@@ -115,6 +119,45 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
115119 "consider adding a safety comment on the preceding line" ,
116120 ) ;
117121 }
122+
123+ if let Some ( tail) = block. expr
124+ && !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , tail. hir_id )
125+ && !in_external_macro ( cx. tcx . sess , tail. span )
126+ && let HasSafetyComment :: Yes ( pos) = stmt_has_safety_comment ( cx, tail. span , tail. hir_id )
127+ && let Some ( help_span) = expr_has_unnecessary_safety_comment ( cx, tail, pos)
128+ {
129+ span_lint_and_help (
130+ cx,
131+ UNNECESSARY_SAFETY_COMMENT ,
132+ tail. span ,
133+ "expression has unnecessary safety comment" ,
134+ Some ( help_span) ,
135+ "consider removing the safety comment" ,
136+ ) ;
137+ }
138+ }
139+
140+ fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & hir:: Stmt < ' tcx > ) {
141+ let expr = match stmt. kind {
142+ hir:: StmtKind :: Local ( & hir:: Local { init : Some ( expr) , .. } )
143+ | hir:: StmtKind :: Expr ( expr)
144+ | hir:: StmtKind :: Semi ( expr) => expr,
145+ _ => return ,
146+ } ;
147+ if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , stmt. hir_id )
148+ && !in_external_macro ( cx. tcx . sess , stmt. span )
149+ && let HasSafetyComment :: Yes ( pos) = stmt_has_safety_comment ( cx, stmt. span , stmt. hir_id )
150+ && let Some ( help_span) = expr_has_unnecessary_safety_comment ( cx, expr, pos)
151+ {
152+ span_lint_and_help (
153+ cx,
154+ UNNECESSARY_SAFETY_COMMENT ,
155+ stmt. span ,
156+ "statement has unnecessary safety comment" ,
157+ Some ( help_span) ,
158+ "consider removing the safety comment" ,
159+ ) ;
160+ }
118161 }
119162
120163 fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) {
@@ -216,6 +259,36 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
216259 }
217260}
218261
262+ fn expr_has_unnecessary_safety_comment < ' tcx > (
263+ cx : & LateContext < ' tcx > ,
264+ expr : & ' tcx hir:: Expr < ' tcx > ,
265+ comment_pos : BytePos ,
266+ ) -> Option < Span > {
267+ // this should roughly be the reverse of `block_parents_have_safety_comment`
268+ if for_each_expr_with_closures ( cx, expr, |expr| match expr. kind {
269+ hir:: ExprKind :: Block (
270+ Block {
271+ rules : BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided ) ,
272+ ..
273+ } ,
274+ _,
275+ ) => ControlFlow :: Break ( ( ) ) ,
276+ // statements will be handled by check_stmt itself again
277+ hir:: ExprKind :: Block ( ..) => ControlFlow :: Continue ( Descend :: No ) ,
278+ _ => ControlFlow :: Continue ( Descend :: Yes ) ,
279+ } )
280+ . is_some ( )
281+ {
282+ return None ;
283+ }
284+
285+ let source_map = cx. tcx . sess . source_map ( ) ;
286+ let span = Span :: new ( comment_pos, comment_pos, SyntaxContext :: root ( ) , None ) ;
287+ let help_span = source_map. span_extend_to_next_char ( span, '\n' , true ) ;
288+
289+ Some ( help_span)
290+ }
291+
219292fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , span : Span ) -> bool {
220293 let source_map = cx. sess ( ) . source_map ( ) ;
221294 let file_pos = source_map. lookup_byte_offset ( span. lo ( ) ) ;
@@ -358,6 +431,55 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
358431 }
359432}
360433
434+ /// Checks if the lines immediately preceding the item contain a safety comment.
435+ #[ allow( clippy:: collapsible_match) ]
436+ fn stmt_has_safety_comment ( cx : & LateContext < ' _ > , span : Span , hir_id : HirId ) -> HasSafetyComment {
437+ match span_from_macro_expansion_has_safety_comment ( cx, span) {
438+ HasSafetyComment :: Maybe => ( ) ,
439+ has_safety_comment => return has_safety_comment,
440+ }
441+
442+ if span. ctxt ( ) == SyntaxContext :: root ( ) {
443+ if let Some ( parent_node) = get_parent_node ( cx. tcx , hir_id) {
444+ let comment_start = match parent_node {
445+ Node :: Block ( block) => walk_span_to_context ( block. span , SyntaxContext :: root ( ) ) . map ( Span :: lo) ,
446+ _ => return HasSafetyComment :: Maybe ,
447+ } ;
448+
449+ let source_map = cx. sess ( ) . source_map ( ) ;
450+ if let Some ( comment_start) = comment_start
451+ && let Ok ( unsafe_line) = source_map. lookup_line ( span. lo ( ) )
452+ && let Ok ( comment_start_line) = source_map. lookup_line ( comment_start)
453+ && Lrc :: ptr_eq ( & unsafe_line. sf , & comment_start_line. sf )
454+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
455+ {
456+ unsafe_line. sf . lines ( |lines| {
457+ if comment_start_line. line >= unsafe_line. line {
458+ HasSafetyComment :: No
459+ } else {
460+ match text_has_safety_comment (
461+ src,
462+ & lines[ comment_start_line. line + 1 ..=unsafe_line. line ] ,
463+ unsafe_line. sf . start_pos . to_usize ( ) ,
464+ ) {
465+ Some ( b) => HasSafetyComment :: Yes ( b) ,
466+ None => HasSafetyComment :: No ,
467+ }
468+ }
469+ } )
470+ } else {
471+ // Problem getting source text. Pretend a comment was found.
472+ HasSafetyComment :: Maybe
473+ }
474+ } else {
475+ // No parent node. Pretend a comment was found.
476+ HasSafetyComment :: Maybe
477+ }
478+ } else {
479+ HasSafetyComment :: No
480+ }
481+ }
482+
361483fn comment_start_before_item_in_mod (
362484 cx : & LateContext < ' _ > ,
363485 parent_mod : & hir:: Mod < ' _ > ,
0 commit comments