1- use clippy_utils:: diagnostics:: span_lint_and_sugg;
1+ use clippy_utils:: diagnostics:: { span_lint_and_help , span_lint_and_sugg} ;
22use clippy_utils:: source:: { snippet, indent_of, reindent_multiline} ;
33use rustc_errors:: Applicability ;
4- use rustc_hir:: { Block , BlockCheckMode , ExprKind , HirId , Local , UnsafeSource } ;
4+ use rustc_hir:: { Block , BlockCheckMode , Expr , ExprKind , HirId , Local , MatchSource , UnsafeSource , YieldSource } ;
55use rustc_lexer:: TokenKind ;
66use rustc_lint:: { LateContext , LateLintPass } ;
77use rustc_middle:: lint:: in_external_macro;
88use rustc_middle:: ty:: TyCtxt ;
99use rustc_session:: { impl_lint_pass, declare_tool_lint} ;
10- use rustc_span:: Span ;
10+ use rustc_span:: { BytePos , Span } ;
1111use std:: borrow:: Cow ;
1212use clippy_utils:: is_lint_allowed;
1313
@@ -46,23 +46,25 @@ impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
4646
4747#[ derive( Default ) ]
4848pub struct UndocumentedUnsafeBlocks {
49- pub local : bool
49+ pub local_level : u32 ,
50+ }
51+
52+ enum ErrorKind {
53+ NoComment ,
54+ Placeholder
5055}
5156
5257impl LateLintPass < ' _ > for UndocumentedUnsafeBlocks {
5358 fn check_block ( & mut self , cx : & LateContext < ' _ > , block : & ' _ Block < ' _ > ) {
54- if self . local {
55- self . local = false ;
56- return
57- }
58-
59- if_chain ! {
60- if !is_lint_allowed( cx, UNDOCUMENTED_UNSAFE_BLOCKS , block. hir_id) ;
61- if !in_external_macro( cx. tcx. sess, block. span) ;
62- if let BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided ) = block. rules;
63- if let Some ( enclosing_scope_hir_id) = cx. tcx. hir( ) . get_enclosing_scope( block. hir_id) ;
64- then {
65- find_candidate( cx, block. span, enclosing_scope_hir_id)
59+ if self . local_level == 0 {
60+ if_chain ! {
61+ if !is_lint_allowed( cx, UNDOCUMENTED_UNSAFE_BLOCKS , block. hir_id) ;
62+ if !in_external_macro( cx. tcx. sess, block. span) ;
63+ if let BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided ) = block. rules;
64+ if let Some ( enclosing_scope_hir_id) = cx. tcx. hir( ) . get_enclosing_scope( block. hir_id) ;
65+ then {
66+ find_candidate( cx, block. span, enclosing_scope_hir_id)
67+ }
6668 }
6769 }
6870 }
@@ -72,71 +74,149 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
7274 if !is_lint_allowed( cx, UNDOCUMENTED_UNSAFE_BLOCKS , local. hir_id) ;
7375 if !in_external_macro( cx. tcx. sess, local. span) ;
7476 if let Some ( init) = local. init;
75- if let ExprKind :: Block ( block, _ ) = init. kind;
77+ if let Some ( ( block, element_count ) ) = find_block_in_expr ( & init. kind) ;
7678 if let BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided ) = block. rules;
7779 if let Some ( enclosing_scope_hir_id) = cx. tcx. hir( ) . get_enclosing_scope( block. hir_id) ;
7880 then {
79- self . local = true ;
81+ self . local_level = self . local_level. saturating_add( element_count) ;
82+
8083 find_candidate( cx, local. span, enclosing_scope_hir_id)
8184 }
8285 }
8386 }
87+
88+ fn check_block_post ( & mut self , _: & LateContext < ' _ > , _: & ' _ Block < ' _ > ) {
89+ self . local_level = self . local_level . saturating_sub ( 1 ) ;
90+ }
8491}
8592
86- fn find_candidate ( cx : & LateContext < ' _ > , span : Span , enclosing_hir_id : HirId ) {
87- if let Some ( true ) = block_has_safety_comment ( cx. tcx , enclosing_hir_id, span) {
88- return ;
93+ fn find_block_in_expr ( expr_kind : & ExprKind < ' hir > ) -> Option < ( & ' tcx Block < ' hir > , u32 ) > {
94+ match expr_kind {
95+ ExprKind :: Array ( elements) | ExprKind :: Tup ( elements) => {
96+ let unsafe_blocks = elements. iter ( ) . filter_map ( |e|
97+ match e {
98+ Expr {
99+ kind : ExprKind :: Block ( block @ Block {
100+ rules : BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided ) ,
101+ ..
102+ } , _) ,
103+ ..
104+ } => Some ( block) ,
105+ _ => None
106+ } ) . collect :: < Vec < _ > > ( ) ;
107+
108+ if let Some ( block) = unsafe_blocks. get ( 0 ) {
109+ return Some ( ( block, unsafe_blocks. len ( ) . try_into ( ) . unwrap ( ) ) )
110+ }
111+
112+ None
113+ } ,
114+ ExprKind :: Box ( Expr {
115+ kind : ExprKind :: Block ( block, _) ,
116+ ..
117+ } ) |
118+ ExprKind :: Unary ( _, Expr {
119+ kind : ExprKind :: Block ( block, _) ,
120+ ..
121+ } ) | ExprKind :: Match ( Expr {
122+ kind : ExprKind :: Block ( block, _) ,
123+ ..
124+ } , _, MatchSource :: Normal ) | ExprKind :: Loop ( block, _, _, _) | ExprKind :: Block ( block, _) | ExprKind :: AddrOf ( _, _, Expr {
125+ kind : ExprKind :: Block ( block, _) ,
126+ ..
127+ } ) | ExprKind :: Break ( _, Some ( Expr {
128+ kind : ExprKind :: Block ( block, _) ,
129+ ..
130+ } ) ) | ExprKind :: Ret ( Some ( Expr {
131+ kind : ExprKind :: Block ( block, _) ,
132+ ..
133+ } ) ) | ExprKind :: Repeat ( Expr {
134+ kind : ExprKind :: Block ( block, _) ,
135+ ..
136+ } , _) | ExprKind :: Yield ( Expr {
137+ kind : ExprKind :: Block ( block, _) ,
138+ ..
139+ } , YieldSource :: Yield ) => Some ( ( block, 1 ) ) ,
140+ _ => None
89141 }
142+ }
90143
91- let block_indent = indent_of ( cx, span) ;
92- let suggestion = format ! ( "// Safety: ...\n {}" , snippet( cx, span, ".." ) ) ;
93-
94- span_lint_and_sugg (
95- cx,
96- UNDOCUMENTED_UNSAFE_BLOCKS ,
97- span,
98- "unsafe block missing a safety comment" ,
99- "consider adding a safety comment" ,
100- reindent_multiline ( Cow :: Borrowed ( & suggestion) , true , block_indent) . to_string ( ) ,
101- Applicability :: HasPlaceholders ,
102- ) ;
144+ fn find_candidate ( cx : & LateContext < ' _ > , span : Span , enclosing_hir_id : HirId ) {
145+ if let Some ( error_kind) = block_has_safety_comment ( cx. tcx , enclosing_hir_id, span) {
146+ match error_kind {
147+ ErrorKind :: NoComment => {
148+ let block_indent = indent_of ( cx, span) ;
149+ let suggestion = format ! ( "// Safety: ...\n {}" , snippet( cx, span, ".." ) ) ;
150+
151+ span_lint_and_sugg (
152+ cx,
153+ UNDOCUMENTED_UNSAFE_BLOCKS ,
154+ span,
155+ "unsafe block missing a safety comment" ,
156+ "consider adding a safety comment" ,
157+ reindent_multiline ( Cow :: Borrowed ( & suggestion) , true , block_indent) . to_string ( ) ,
158+ Applicability :: HasPlaceholders ,
159+ ) ;
160+ }
161+ ErrorKind :: Placeholder => {
162+ span_lint_and_help (
163+ cx,
164+ UNDOCUMENTED_UNSAFE_BLOCKS ,
165+ span,
166+ "unsafe block has safety comment placeholder" ,
167+ None ,
168+ "consider replacing the placeholder with a safety comment"
169+ ) ;
170+ }
171+ }
172+ }
103173}
104174
105- fn block_has_safety_comment ( tcx : TyCtxt < ' _ > , enclosing_hir_id : HirId , block_span : Span ) -> Option < bool > {
175+ fn block_has_safety_comment ( tcx : TyCtxt < ' _ > , enclosing_hir_id : HirId , block_span : Span ) -> Option < ErrorKind > {
106176 let map = tcx. hir ( ) ;
107177 let source_map = tcx. sess . source_map ( ) ;
108178
109179 let enclosing_scope_span = map. opt_span ( enclosing_hir_id) ?;
110- let between_span = enclosing_scope_span. until ( block_span) ;
180+ let between_span = enclosing_scope_span. to ( block_span) ;
111181
112182 let file_name = source_map. span_to_filename ( between_span) ;
113183 let source_file = source_map. get_source_file ( & file_name) ?;
114184
115185 let lex_start = ( between_span. lo ( ) . 0 + 1 ) as usize ;
116- let mut src_str = source_file. src . as_ref ( ) ?[ lex_start..between_span. hi ( ) . 0 as usize ] . to_string ( ) ;
117-
118- src_str. retain ( |c| !c. is_whitespace ( ) ) ;
119-
120- let src_str_split = src_str. lines ( ) . rev ( ) . collect :: < Vec < _ > > ( ) ;
121- let src_str = src_str_split. join ( "" ) ;
186+ let src_str = source_file. src . as_ref ( ) ?[ lex_start..between_span. hi ( ) . 0 as usize ] . to_string ( ) ;
122187
123188 let mut pos = 0 ;
124- let mut found_safety_comment = false ;
189+ let mut comment = false ;
125190
126191 for token in rustc_lexer:: tokenize ( & src_str) {
127192 match token. kind {
128193 TokenKind :: LineComment { doc_style : None }
129- | TokenKind :: BlockComment { doc_style : None , terminated : true } => {
130- if src_str[ pos + 2 .. pos + token. len ] . to_ascii_uppercase ( ) . contains ( "SAFETY:" ) {
131- found_safety_comment = true ;
132- break
194+ | TokenKind :: BlockComment { doc_style : None , terminated : true } => {
195+ let comment_str = src_str[ pos + 2 ..pos + token. len ] . to_ascii_uppercase ( ) ;
196+
197+ if comment_str. contains ( "SAFETY: ..." ) {
198+ return Some ( ErrorKind :: Placeholder ) ;
199+ } else if comment_str. contains ( "SAFETY:" ) {
200+ comment = true ;
201+ }
202+ } ,
203+ TokenKind :: Whitespace => { } ,
204+ _ => {
205+ if comment {
206+ let comment_line_num = source_file. lookup_file_pos_with_col_display ( BytePos ( ( lex_start + pos) . try_into ( ) . unwrap ( ) ) ) . 0 ;
207+ let block_line_num = tcx. sess . source_map ( ) . lookup_char_pos ( block_span. lo ( ) ) . line ;
208+
209+ if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
210+ return None ;
211+ }
212+
213+ comment = false ;
133214 }
134215 } ,
135- _ => break
136216 }
137217
138218 pos += token. len ;
139219 }
140220
141- Some ( found_safety_comment )
221+ Some ( ErrorKind :: NoComment )
142222}
0 commit comments