@@ -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.
467517#[ allow( clippy:: collapsible_match) ]
468518fn item_has_safety_comment (
@@ -522,8 +572,8 @@ fn item_has_safety_comment(
522572 unsafe_line. sf . start_pos ,
523573 accept_comment_above_attributes,
524574 ) {
525- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
526- None => HasSafetyComment :: No ,
575+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
576+ SafetyComment :: Absent => HasSafetyComment :: No ,
527577 }
528578 } ;
529579 }
@@ -568,8 +618,8 @@ fn stmt_has_safety_comment(
568618 unsafe_line. sf . start_pos ,
569619 accept_comment_above_attributes,
570620 ) {
571- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
572- None => HasSafetyComment :: No ,
621+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
622+ SafetyComment :: Absent => HasSafetyComment :: No ,
573623 }
574624 } ;
575625 }
@@ -649,8 +699,8 @@ fn span_from_macro_expansion_has_safety_comment(
649699 unsafe_line. sf . start_pos ,
650700 accept_comment_above_attributes,
651701 ) {
652- Some ( b ) => HasSafetyComment :: Yes ( b ) ,
653- None => HasSafetyComment :: No ,
702+ SafetyComment :: Present { pos , is_doc } => HasSafetyComment :: Yes ( pos , is_doc ) ,
703+ SafetyComment :: Absent => HasSafetyComment :: No ,
654704 }
655705 } else {
656706 HasSafetyComment :: No
@@ -710,13 +760,15 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abov
710760 // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
711761 // ^-------------^
712762 body_line. line < unsafe_line. line
713- && text_has_safety_comment (
714- src,
715- & unsafe_line. sf . lines ( ) [ body_line. line + 1 ..=unsafe_line. line ] ,
716- unsafe_line. sf . start_pos ,
717- accept_comment_above_attributes,
763+ && matches ! (
764+ text_has_safety_comment(
765+ src,
766+ & unsafe_line. sf. lines( ) [ body_line. line + 1 ..=unsafe_line. line] ,
767+ unsafe_line. sf. start_pos,
768+ accept_comment_above_attributes,
769+ ) ,
770+ SafetyComment :: Present { .. }
718771 )
719- . is_some ( )
720772 } else {
721773 // Problem getting source text. Pretend a comment was found.
722774 true
@@ -735,7 +787,7 @@ fn text_has_safety_comment(
735787 line_starts : & [ RelativeBytePos ] ,
736788 start_pos : BytePos ,
737789 accept_comment_above_attributes : bool ,
738- ) -> Option < BytePos > {
790+ ) -> SafetyComment {
739791 let mut lines = line_starts
740792 . array_windows :: < 2 > ( )
741793 . rev ( )
@@ -748,7 +800,10 @@ fn text_has_safety_comment(
748800 } )
749801 . filter ( |( _, text) | !( text. is_empty ( ) || ( accept_comment_above_attributes && is_attribute ( text) ) ) ) ;
750802
751- let ( line_start, line) = lines. next ( ) ?;
803+ let Some ( ( line_start, line) ) = lines. next ( ) else {
804+ return SafetyComment :: Absent ;
805+ } ;
806+
752807 let mut in_codeblock = false ;
753808 // Check for a sequence of line comments.
754809 if line. starts_with ( "//" ) {
@@ -761,12 +816,15 @@ fn text_has_safety_comment(
761816 in_codeblock = !in_codeblock;
762817 }
763818
764- if line. to_ascii_uppercase ( ) . contains ( "SAFETY:" ) && !in_codeblock {
765- return Some ( start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ) ;
819+ if !in_codeblock && line. to_ascii_uppercase ( ) . contains ( "SAFETY:" ) {
820+ return SafetyComment :: Present {
821+ pos : start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ,
822+ is_doc : line. starts_with ( "///" ) ,
823+ } ;
766824 }
767825 match lines. next ( ) {
768826 Some ( ( s, x) ) if x. starts_with ( "//" ) => ( line, line_start) = ( x, s) ,
769- _ => return None ,
827+ _ => return SafetyComment :: Absent ,
770828 }
771829 }
772830 }
@@ -777,15 +835,22 @@ fn text_has_safety_comment(
777835 if line. starts_with ( "/*" ) {
778836 let src = & src[ line_start..line_starts. last ( ) . unwrap ( ) . to_usize ( ) ] ;
779837 let mut tokens = tokenize ( src, FrontmatterAllowed :: No ) ;
780- return ( src[ ..tokens. next ( ) . unwrap ( ) . len as usize ]
781- . to_ascii_uppercase ( )
782- . contains ( "SAFETY:" )
783- && tokens. all ( |t| t. kind == TokenKind :: Whitespace ) )
784- . then_some ( start_pos + BytePos ( u32:: try_from ( line_start) . unwrap ( ) ) ) ;
838+ let a = tokens. next ( ) ;
839+ if let Some ( safety_pos) = src[ ..a. unwrap ( ) . len as usize ] . to_ascii_uppercase ( ) . find ( "SAFETY:" )
840+ && tokens. all ( |t| t. kind == TokenKind :: Whitespace )
841+ {
842+ return SafetyComment :: Present {
843+ pos : start_pos
844+ + BytePos ( u32:: try_from ( line_start) . unwrap ( ) )
845+ + BytePos ( u32:: try_from ( safety_pos) . unwrap ( ) ) ,
846+ is_doc : line. starts_with ( "/**" ) ,
847+ } ;
848+ }
849+ return SafetyComment :: Absent ;
785850 }
786851 match lines. next ( ) {
787852 Some ( x) => ( line_start, line) = x,
788- None => return None ,
853+ None => return SafetyComment :: Absent ,
789854 }
790855 }
791856}
0 commit comments