11use clippy_utils:: diagnostics:: span_lint_and_help;
2- use clippy_utils:: is_lint_allowed;
32use clippy_utils:: source:: walk_span_to_context;
3+ use clippy_utils:: { get_parent_node, is_lint_allowed} ;
44use rustc_data_structures:: sync:: Lrc ;
55use rustc_hir as hir;
6- use rustc_hir:: { Block , BlockCheckMode , UnsafeSource } ;
6+ use rustc_hir:: { Block , BlockCheckMode , ItemKind , Node , UnsafeSource } ;
77use rustc_lexer:: { tokenize, TokenKind } ;
88use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
99use rustc_middle:: lint:: in_external_macro;
1010use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1111use rustc_span:: { BytePos , Pos , Span , SyntaxContext } ;
12- use std:: rc:: Rc ;
1312
1413declare_clippy_lint ! {
1514 /// ### What it does
16- /// Checks for `unsafe` blocks without a `// SAFETY: ` comment
15+ /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
1716 /// explaining why the unsafe operations performed inside
1817 /// the block are safe.
1918 ///
@@ -36,7 +35,7 @@ declare_clippy_lint! {
3635 /// ```
3736 ///
3837 /// ### Why is this bad?
39- /// Undocumented unsafe blocks can make it difficult to
38+ /// Undocumented unsafe blocks and impls can make it difficult to
4039 /// read and maintain code, as well as uncover unsoundness
4140 /// and bugs.
4241 ///
@@ -68,7 +67,7 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
6867 if block. rules == BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided )
6968 && !in_external_macro ( cx. tcx . sess , block. span )
7069 && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , block. hir_id )
71- && !is_unsafe_from_proc_macro ( cx, block)
70+ && !is_unsafe_from_proc_macro ( cx, block. span )
7271 && !block_has_safety_comment ( cx, block)
7372 {
7473 let source_map = cx. tcx . sess . source_map ( ) ;
@@ -89,70 +88,36 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
8988 }
9089 }
9190
92- fn check_mod ( & mut self , cx : & LateContext < ' _ > , module : & ' _ hir:: Mod < ' _ > , mod_span : Span , hir_id : hir:: HirId ) {
93- let source_map = cx. sess ( ) . source_map ( ) ;
94- let mut item_and_spans: Vec < ( & hir:: Item < ' _ > , Span ) > = Vec :: new ( ) ; // (start, end, item)
95-
96- // Collect all items and their spans
97- for item_id in module. item_ids {
98- let item = cx. tcx . hir ( ) . item ( * item_id) ;
99- item_and_spans. push ( ( item, item. span ) ) ;
100- }
101- // Sort items by start position
102- item_and_spans. sort_by_key ( |e| e. 1 . lo ( ) ) ;
103-
104- for ( idx, ( item, item_span) ) in item_and_spans. iter ( ) . enumerate ( ) {
105- if let hir:: ItemKind :: Impl ( imple) = & item. kind
106- && imple. unsafety == hir:: Unsafety :: Unsafe
107- && !item_span. from_expansion ( )
108- && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , hir_id)
109- {
110- // Checks if the lines immediately preceding the impl contain a safety comment.
111- let impl_has_safety_comment = {
112- let span_before_impl = if idx == 0 {
113- // mod A { /* comment */ unsafe impl T {} }
114- // ^--------------------^
115- todo ! ( ) ;
116- //mod_span.until(module.spans)
117- } else {
118- // unsafe impl S {} /* comment */ unsafe impl T {}
119- // ^-------------^
120- item_and_spans[ idx - 1 ] . 1 . between ( * item_span)
121- } ;
122-
123- if let Ok ( start) = source_map. lookup_line ( span_before_impl. lo ( ) )
124- && let Ok ( end) = source_map. lookup_line ( span_before_impl. hi ( ) )
125- && let Some ( src) = start. sf . src . as_deref ( )
126- {
127- start. line < end. line && text_has_safety_comment (
128- src,
129- & start. sf . lines [ start. line + 1 ..= end. line ] ,
130- start. sf . start_pos . to_usize ( )
131- )
132- } else {
133- // Problem getting source text. Pretend a comment was found.
134- true
135- }
136- } ;
91+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) {
92+ if let hir:: ItemKind :: Impl ( imple) = item. kind
93+ && imple. unsafety == hir:: Unsafety :: Unsafe
94+ && !in_external_macro ( cx. tcx . sess , item. span )
95+ && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , item. hir_id ( ) )
96+ && !is_unsafe_from_proc_macro ( cx, item. span )
97+ && !item_has_safety_comment ( cx, item)
98+ {
99+ let source_map = cx. tcx . sess . source_map ( ) ;
100+ let span = if source_map. is_multiline ( item. span ) {
101+ source_map. span_until_char ( item. span , '\n' )
102+ } else {
103+ item. span
104+ } ;
137105
138- if !impl_has_safety_comment {
139- span_lint_and_help (
140- cx,
141- UNDOCUMENTED_UNSAFE_BLOCKS ,
142- * item_span,
143- "unsafe impl missing a safety comment" ,
144- None ,
145- "consider adding a safety comment on the preceding line" ,
146- ) ;
147- }
148- }
106+ span_lint_and_help (
107+ cx,
108+ UNDOCUMENTED_UNSAFE_BLOCKS ,
109+ span,
110+ "unsafe impl missing a safety comment" ,
111+ None ,
112+ "consider adding a safety comment on the preceding line" ,
113+ ) ;
149114 }
150115 }
151116}
152117
153- fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
118+ fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , span : Span ) -> bool {
154119 let source_map = cx. sess ( ) . source_map ( ) ;
155- let file_pos = source_map. lookup_byte_offset ( block . span . lo ( ) ) ;
120+ let file_pos = source_map. lookup_byte_offset ( span. lo ( ) ) ;
156121 file_pos
157122 . sf
158123 . src
@@ -162,7 +127,7 @@ fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
162127}
163128
164129/// Checks if the lines immediately preceding the block contain a safety comment.
165- fn block_has_safety_comment ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
130+ fn block_has_safety_comment ( cx : & LateContext < ' _ > , block : & hir :: Block < ' _ > ) -> bool {
166131 // This intentionally ignores text before the start of a function so something like:
167132 // ```
168133 // // SAFETY: reason
@@ -171,13 +136,85 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
171136 // won't work. This is to avoid dealing with where such a comment should be place relative to
172137 // attributes and doc comments.
173138
139+ span_from_macro_expansion_has_safety_comment ( cx, block. span ) || span_in_body_has_safety_comment ( cx, block. span )
140+ }
141+
142+ /// Checks if the lines immediately preceding the item contain a safety comment.
143+ fn item_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) -> bool {
144+ if span_from_macro_expansion_has_safety_comment ( cx, item. span ) || span_in_body_has_safety_comment ( cx, item. span ) {
145+ return true ;
146+ }
147+
148+ if item. span . ctxt ( ) == SyntaxContext :: root ( ) {
149+ if let Some ( parent_node) = get_parent_node ( cx. tcx , item. hir_id ( ) ) {
150+ let mut span_before_item = None ;
151+ let mut hi = false ;
152+ if let Node :: Item ( parent_item) = parent_node {
153+ if let ItemKind :: Mod ( parent_mod) = & parent_item. kind {
154+ for ( idx, item_id) in parent_mod. item_ids . iter ( ) . enumerate ( ) {
155+ if * item_id == item. item_id ( ) {
156+ if idx == 0 {
157+ // mod A { /* comment */ unsafe impl T {} ... }
158+ // ^------------------------------------------^ gets this span
159+ // ^---------------------^ finally checks the text in this range
160+ hi = false ;
161+ span_before_item = Some ( parent_item. span ) ;
162+ } else {
163+ let prev_item = cx. tcx . hir ( ) . item ( parent_mod. item_ids [ idx - 1 ] ) ;
164+ // some_item /* comment */ unsafe impl T {}
165+ // ^-------^ gets this span
166+ // ^---------------^ finally checks the text in this range
167+ hi = true ;
168+ span_before_item = Some ( prev_item. span ) ;
169+ }
170+ break ;
171+ }
172+ }
173+ }
174+ }
175+ let span_before_item = span_before_item. unwrap ( ) ;
176+
177+ let source_map = cx. sess ( ) . source_map ( ) ;
178+ if let Some ( item_span) = walk_span_to_context ( item. span , SyntaxContext :: root ( ) )
179+ && let Some ( span_before_item) = walk_span_to_context ( span_before_item, SyntaxContext :: root ( ) )
180+ && let Ok ( unsafe_line) = source_map. lookup_line ( item_span. lo ( ) )
181+ && let Ok ( line_before_unsafe) = source_map. lookup_line ( if hi {
182+ span_before_item. hi ( )
183+ } else {
184+ span_before_item. lo ( )
185+ } )
186+ && Lrc :: ptr_eq ( & unsafe_line. sf , & line_before_unsafe. sf )
187+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
188+ {
189+ line_before_unsafe. line < unsafe_line. line && text_has_safety_comment (
190+ src,
191+ & unsafe_line. sf . lines [ line_before_unsafe. line + 1 ..=unsafe_line. line ] ,
192+ unsafe_line. sf . start_pos . to_usize ( ) ,
193+ )
194+ } else {
195+ // Problem getting source text. Pretend a comment was found.
196+ true
197+ }
198+ } else {
199+ // No parent node. Pretend a comment was found.
200+ true
201+ }
202+ } else {
203+ false
204+ }
205+ }
206+
207+ fn span_from_macro_expansion_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> bool {
174208 let source_map = cx. sess ( ) . source_map ( ) ;
175- let ctxt = block. span . ctxt ( ) ;
176- if ctxt != SyntaxContext :: root ( ) {
177- // From a macro expansion. Get the text from the start of the macro declaration to start of the unsafe block.
209+ let ctxt = span. ctxt ( ) ;
210+ if ctxt == SyntaxContext :: root ( ) {
211+ false
212+ } else {
213+ // From a macro expansion. Get the text from the start of the macro declaration to start of the
214+ // unsafe block.
178215 // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
179216 // ^--------------------------------------------^
180- if let Ok ( unsafe_line) = source_map. lookup_line ( block . span . lo ( ) )
217+ if let Ok ( unsafe_line) = source_map. lookup_line ( span. lo ( ) )
181218 && let Ok ( macro_line) = source_map. lookup_line ( ctxt. outer_expn_data ( ) . def_site . lo ( ) )
182219 && Lrc :: ptr_eq ( & unsafe_line. sf , & macro_line. sf )
183220 && let Some ( src) = unsafe_line. sf . src . as_deref ( )
@@ -191,24 +228,35 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
191228 // Problem getting source text. Pretend a comment was found.
192229 true
193230 }
194- } else if let Ok ( unsafe_line) = source_map. lookup_line ( block. span . lo ( ) )
231+ }
232+ }
233+
234+ fn span_in_body_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> bool {
235+ let source_map = cx. sess ( ) . source_map ( ) ;
236+ let ctxt = span. ctxt ( ) ;
237+ if ctxt == SyntaxContext :: root ( )
195238 && let Some ( body) = cx. enclosing_body
196- && let Some ( body_span) = walk_span_to_context ( cx. tcx . hir ( ) . body ( body) . value . span , SyntaxContext :: root ( ) )
197- && let Ok ( body_line) = source_map. lookup_line ( body_span. lo ( ) )
198- && Lrc :: ptr_eq ( & unsafe_line. sf , & body_line. sf )
199- && let Some ( src) = unsafe_line. sf . src . as_deref ( )
200239 {
201- // Get the text from the start of function body to the unsafe block.
202- // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
203- // ^-------------^
204- body_line. line < unsafe_line. line && text_has_safety_comment (
205- src,
206- & unsafe_line. sf . lines [ body_line. line + 1 ..=unsafe_line. line ] ,
207- unsafe_line. sf . start_pos . to_usize ( ) ,
208- )
240+ if let Ok ( unsafe_line) = source_map. lookup_line ( span. lo ( ) )
241+ && let Some ( body_span) = walk_span_to_context ( cx. tcx . hir ( ) . body ( body) . value . span , SyntaxContext :: root ( ) )
242+ && let Ok ( body_line) = source_map. lookup_line ( body_span. lo ( ) )
243+ && Lrc :: ptr_eq ( & unsafe_line. sf , & body_line. sf )
244+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
245+ {
246+ // Get the text from the start of function body to the unsafe block.
247+ // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
248+ // ^-------------^
249+ body_line. line < unsafe_line. line && text_has_safety_comment (
250+ src,
251+ & unsafe_line. sf . lines [ body_line. line + 1 ..=unsafe_line. line ] ,
252+ unsafe_line. sf . start_pos . to_usize ( ) ,
253+ )
254+ } else {
255+ // Problem getting source text. Pretend a comment was found.
256+ true
257+ }
209258 } else {
210- // Problem getting source text. Pretend a comment was found.
211- true
259+ false
212260 }
213261}
214262
0 commit comments