@@ -7,8 +7,8 @@ use clippy_utils::is_lint_allowed;
77use clippy_utils:: source:: walk_span_to_context;
88use clippy_utils:: visitors:: { Descend , for_each_expr} ;
99use hir:: HirId ;
10- use rustc_hir as hir ;
11- use rustc_hir:: { Block , BlockCheckMode , Impl , ItemKind , Node , UnsafeSource } ;
10+ use rustc_errors :: Applicability ;
11+ use rustc_hir:: { self as hir , Block , BlockCheckMode , FnSig , Impl , ItemKind , Node , UnsafeSource } ;
1212use rustc_lexer:: { FrontmatterAllowed , TokenKind , tokenize} ;
1313use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1414use rustc_session:: impl_lint_pass;
@@ -143,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
143143 if let Some ( tail) = block. expr
144144 && !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , tail. hir_id )
145145 && !tail. span . in_external_macro ( cx. tcx . sess . source_map ( ) )
146- && let HasSafetyComment :: Yes ( pos) =
146+ && let HasSafetyComment :: Yes ( pos, _ ) =
147147 stmt_has_safety_comment ( cx, tail. span , tail. hir_id , self . accept_comment_above_attributes )
148148 && let Some ( help_span) = expr_has_unnecessary_safety_comment ( cx, tail, pos)
149149 {
@@ -168,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
168168 } ;
169169 if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , stmt. hir_id )
170170 && !stmt. span . in_external_macro ( cx. tcx . sess . source_map ( ) )
171- && let HasSafetyComment :: Yes ( pos) =
171+ && let HasSafetyComment :: Yes ( pos, _ ) =
172172 stmt_has_safety_comment ( cx, stmt. span , stmt. hir_id , self . accept_comment_above_attributes )
173173 && let Some ( help_span) = expr_has_unnecessary_safety_comment ( cx, expr, pos)
174174 {
@@ -203,14 +203,14 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
203203
204204 let item_has_safety_comment = item_has_safety_comment ( cx, item, self . accept_comment_above_attributes ) ;
205205 match item_has_safety_comment {
206- HasSafetyComment :: Yes ( pos) => check_has_safety_comment ( cx, item, mk_spans ( pos) ) ,
206+ HasSafetyComment :: Yes ( pos, is_doc ) => check_has_safety_comment ( cx, item, mk_spans ( pos) , is_doc ) ,
207207 HasSafetyComment :: No => check_has_no_safety_comment ( cx, item) ,
208208 HasSafetyComment :: Maybe => { } ,
209209 }
210210 }
211211}
212212
213- fn check_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > , ( span, help_span) : ( Span , Span ) ) {
213+ fn check_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > , ( span, help_span) : ( Span , Span ) , is_doc : bool ) {
214214 match & item. kind {
215215 ItemKind :: Impl ( Impl {
216216 of_trait : Some ( of_trait) ,
@@ -252,6 +252,50 @@ fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, h
252252 }
253253 }
254254 } ,
255+ // Unsafe functions with a SAFETY comment are suggested to change it to a `# Safety` comment
256+ ItemKind :: Fn {
257+ sig : FnSig { header, .. } ,
258+ ..
259+ } if header. is_unsafe ( ) => {
260+ if !is_lint_allowed ( cx, UNNECESSARY_SAFETY_COMMENT , item. hir_id ( ) ) {
261+ span_lint_and_then (
262+ cx,
263+ UNNECESSARY_SAFETY_COMMENT ,
264+ span,
265+ format ! (
266+ "{} has safety comment, but maybe a `# Safety` segment would be better" ,
267+ cx. tcx. def_descr( item. owner_id. to_def_id( ) ) ,
268+ ) ,
269+ |diag| {
270+ if is_doc {
271+ // If it's already within a doc comment, we try to suggest the change
272+
273+ let source_map = cx. sess ( ) . source_map ( ) ;
274+ if let Ok ( unsafe_line) = source_map. lookup_line ( item. span . lo ( ) )
275+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
276+ {
277+ let help_pos_lo = source_map. lookup_byte_offset ( help_span. lo ( ) ) ;
278+ let help_pos_hi = source_map. lookup_byte_offset ( help_span. hi ( ) ) ;
279+
280+ diag. span_suggestion (
281+ help_span,
282+ "consider changing it to a `# Safety` section" ,
283+ src. get ( help_pos_lo. pos . to_usize ( ) ..help_pos_hi. pos . to_usize ( ) )
284+ . unwrap ( )
285+ . replace ( "SAFETY:" , "# Safety" ) ,
286+ Applicability :: MachineApplicable ,
287+ ) ;
288+ }
289+ } else {
290+ diag. span_help (
291+ help_span,
292+ "consider changing the `SAFETY` comment for a `# Safety` doc comment" ,
293+ ) ;
294+ }
295+ } ,
296+ ) ;
297+ }
298+ } ,
255299 // Aside from unsafe impls and consts/statics with an unsafe block, items in general
256300 // do not have safety invariants that need to be documented, so lint those.
257301 _ => {
@@ -453,16 +497,22 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abo
453497
454498 matches ! (
455499 span_from_macro_expansion_has_safety_comment( cx, span, accept_comment_above_attributes) ,
456- HasSafetyComment :: Yes ( _)
500+ HasSafetyComment :: Yes ( _, _ )
457501 ) || span_has_safety_comment ( cx, span, accept_comment_above_attributes)
458502}
459503
504+ #[ derive( Debug ) ]
460505enum HasSafetyComment {
461- Yes ( BytePos ) ,
506+ Yes ( BytePos , bool ) ,
462507 No ,
463508 Maybe ,
464509}
465510
511+ enum SafetyComment {
512+ Present { pos : BytePos , is_doc : bool } ,
513+ Absent ,
514+ }
515+
466516/// Checks if the lines immediately preceding the item contain a safety comment.
467517fn item_has_safety_comment (
468518 cx : & LateContext < ' _ > ,
@@ -521,8 +571,8 @@ fn item_has_safety_comment(
521571 unsafe_line. sf . start_pos ,
522572 accept_comment_above_attributes,
523573 ) {
524- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
525- None => HasSafetyComment :: No ,
574+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
575+ SafetyComment :: Absent => HasSafetyComment :: No ,
526576 }
527577 } ;
528578 }
@@ -566,8 +616,8 @@ fn stmt_has_safety_comment(
566616 unsafe_line. sf . start_pos ,
567617 accept_comment_above_attributes,
568618 ) {
569- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
570- None => HasSafetyComment :: No ,
619+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
620+ SafetyComment :: Absent => HasSafetyComment :: No ,
571621 }
572622 } ;
573623 }
@@ -647,8 +697,8 @@ fn span_from_macro_expansion_has_safety_comment(
647697 unsafe_line. sf . start_pos ,
648698 accept_comment_above_attributes,
649699 ) {
650- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
651- None => HasSafetyComment :: No ,
700+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
701+ SafetyComment :: Absent => HasSafetyComment :: No ,
652702 }
653703 } else {
654704 HasSafetyComment :: No
@@ -708,13 +758,15 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abov
708758 // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
709759 // ^-------------^
710760 body_line. line < unsafe_line. line
711- && text_has_safety_comment (
712- src,
713- & unsafe_line. sf . lines ( ) [ body_line. line + 1 ..=unsafe_line. line ] ,
714- unsafe_line. sf . start_pos ,
715- accept_comment_above_attributes,
761+ && matches ! (
762+ text_has_safety_comment(
763+ src,
764+ & unsafe_line. sf. lines( ) [ body_line. line + 1 ..=unsafe_line. line] ,
765+ unsafe_line. sf. start_pos,
766+ accept_comment_above_attributes,
767+ ) ,
768+ SafetyComment :: Present { .. }
716769 )
717- . is_some ( )
718770 } else {
719771 // Problem getting source text. Pretend a comment was found.
720772 true
@@ -733,7 +785,7 @@ fn text_has_safety_comment(
733785 line_starts : & [ RelativeBytePos ] ,
734786 start_pos : BytePos ,
735787 accept_comment_above_attributes : bool ,
736- ) -> Option < BytePos > {
788+ ) -> SafetyComment {
737789 let mut lines = line_starts
738790 . array_windows :: < 2 > ( )
739791 . rev ( )
@@ -746,7 +798,10 @@ fn text_has_safety_comment(
746798 } )
747799 . filter ( |( _, text) | !( text. is_empty ( ) || ( accept_comment_above_attributes && is_attribute ( text) ) ) ) ;
748800
749- let ( line_start, line) = lines. next ( ) ?;
801+ let Some ( ( line_start, line) ) = lines. next ( ) else {
802+ return SafetyComment :: Absent ;
803+ } ;
804+
750805 let mut in_codeblock = false ;
751806 // Check for a sequence of line comments.
752807 if line. starts_with ( "//" ) {
@@ -759,12 +814,15 @@ fn text_has_safety_comment(
759814 in_codeblock = !in_codeblock;
760815 }
761816
762- if line. to_ascii_uppercase ( ) . contains ( "SAFETY:" ) && !in_codeblock {
763- return Some ( start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ) ;
817+ if !in_codeblock && line. to_ascii_uppercase ( ) . contains ( "SAFETY:" ) {
818+ return SafetyComment :: Present {
819+ pos : start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ,
820+ is_doc : line. starts_with ( "///" ) ,
821+ } ;
764822 }
765823 match lines. next ( ) {
766824 Some ( ( s, x) ) if x. starts_with ( "//" ) => ( line, line_start) = ( x, s) ,
767- _ => return None ,
825+ _ => return SafetyComment :: Absent ,
768826 }
769827 }
770828 }
@@ -775,15 +833,22 @@ fn text_has_safety_comment(
775833 if line. starts_with ( "/*" ) {
776834 let src = & src[ line_start..line_starts. last ( ) . unwrap ( ) . to_usize ( ) ] ;
777835 let mut tokens = tokenize ( src, FrontmatterAllowed :: No ) ;
778- return ( src[ ..tokens. next ( ) . unwrap ( ) . len as usize ]
779- . to_ascii_uppercase ( )
780- . contains ( "SAFETY:" )
781- && tokens. all ( |t| t. kind == TokenKind :: Whitespace ) )
782- . then_some ( start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ) ;
836+ let a = tokens. next ( ) ;
837+ if let Some ( safety_pos) = src[ ..a. unwrap ( ) . len as usize ] . to_ascii_uppercase ( ) . find ( "SAFETY:" )
838+ && tokens. all ( |t| t. kind == TokenKind :: Whitespace )
839+ {
840+ return SafetyComment :: Present {
841+ pos : start_pos
842+ + BytePos ( u32:: try_from ( line_start) . unwrap ( ) )
843+ + BytePos ( u32:: try_from ( safety_pos) . unwrap ( ) ) ,
844+ is_doc : line. starts_with ( "/**" ) ,
845+ } ;
846+ }
847+ return SafetyComment :: Absent ;
783848 }
784849 match lines. next ( ) {
785850 Some ( x) => ( line_start, line) = x,
786- None => return None ,
851+ None => return SafetyComment :: Absent ,
787852 }
788853 }
789854}
0 commit comments