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 ;
5- use rustc_hir:: { Block , BlockCheckMode , UnsafeSource } ;
5+ use rustc_hir as hir;
6+ use rustc_hir:: { Block , BlockCheckMode , ItemKind , Node , UnsafeSource } ;
67use rustc_lexer:: { tokenize, TokenKind } ;
78use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
89use rustc_middle:: lint:: in_external_macro;
910use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
10- use rustc_span:: { BytePos , Pos , SyntaxContext } ;
11+ use rustc_span:: { BytePos , Pos , Span , SyntaxContext } ;
1112
1213declare_clippy_lint ! {
1314 /// ### What it does
14- /// Checks for `unsafe` blocks without a `// SAFETY: ` comment
15+ /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
1516 /// explaining why the unsafe operations performed inside
1617 /// the block are safe.
1718 ///
@@ -34,7 +35,7 @@ declare_clippy_lint! {
3435 /// ```
3536 ///
3637 /// ### Why is this bad?
37- /// Undocumented unsafe blocks can make it difficult to
38+ /// Undocumented unsafe blocks and impls can make it difficult to
3839 /// read and maintain code, as well as uncover unsoundness
3940 /// and bugs.
4041 ///
@@ -66,7 +67,7 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
6667 if block. rules == BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided )
6768 && !in_external_macro ( cx. tcx . sess , block. span )
6869 && !is_lint_allowed ( cx, UNDOCUMENTED_UNSAFE_BLOCKS , block. hir_id )
69- && !is_unsafe_from_proc_macro ( cx, block)
70+ && !is_unsafe_from_proc_macro ( cx, block. span )
7071 && !block_has_safety_comment ( cx, block)
7172 {
7273 let source_map = cx. tcx . sess . source_map ( ) ;
@@ -86,11 +87,37 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
8687 ) ;
8788 }
8889 }
90+
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+ } ;
105+
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+ ) ;
114+ }
115+ }
89116}
90117
91- fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
118+ fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , span : Span ) -> bool {
92119 let source_map = cx. sess ( ) . source_map ( ) ;
93- let file_pos = source_map. lookup_byte_offset ( block . span . lo ( ) ) ;
120+ let file_pos = source_map. lookup_byte_offset ( span. lo ( ) ) ;
94121 file_pos
95122 . sf
96123 . src
@@ -100,7 +127,7 @@ fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
100127}
101128
102129/// Checks if the lines immediately preceding the block contain a safety comment.
103- fn block_has_safety_comment ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
130+ fn block_has_safety_comment ( cx : & LateContext < ' _ > , block : & hir :: Block < ' _ > ) -> bool {
104131 // This intentionally ignores text before the start of a function so something like:
105132 // ```
106133 // // SAFETY: reason
@@ -109,13 +136,115 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
109136 // won't work. This is to avoid dealing with where such a comment should be place relative to
110137 // attributes and doc comments.
111138
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+ #[ allow( clippy:: collapsible_match) ]
144+ fn item_has_safety_comment ( cx : & LateContext < ' _ > , item : & hir:: Item < ' _ > ) -> bool {
145+ if span_from_macro_expansion_has_safety_comment ( cx, item. span ) {
146+ return true ;
147+ }
148+
149+ if item. span . ctxt ( ) == SyntaxContext :: root ( ) {
150+ if let Some ( parent_node) = get_parent_node ( cx. tcx , item. hir_id ( ) ) {
151+ let comment_start = match parent_node {
152+ Node :: Crate ( parent_mod) => {
153+ comment_start_before_impl_in_mod ( cx, parent_mod, parent_mod. spans . inner_span , item)
154+ } ,
155+ Node :: Item ( parent_item) => {
156+ if let ItemKind :: Mod ( parent_mod) = & parent_item. kind {
157+ comment_start_before_impl_in_mod ( cx, parent_mod, parent_item. span , item)
158+ } else {
159+ // Doesn't support impls in this position. Pretend a comment was found.
160+ return true ;
161+ }
162+ } ,
163+ Node :: Stmt ( stmt) => {
164+ if let Some ( stmt_parent) = get_parent_node ( cx. tcx , stmt. hir_id ) {
165+ match stmt_parent {
166+ Node :: Block ( block) => walk_span_to_context ( block. span , SyntaxContext :: root ( ) ) . map ( Span :: lo) ,
167+ _ => {
168+ // Doesn't support impls in this position. Pretend a comment was found.
169+ return true ;
170+ } ,
171+ }
172+ } else {
173+ // Problem getting the parent node. Pretend a comment was found.
174+ return true ;
175+ }
176+ } ,
177+ _ => {
178+ // Doesn't support impls in this position. Pretend a comment was found.
179+ return true ;
180+ } ,
181+ } ;
182+
183+ let source_map = cx. sess ( ) . source_map ( ) ;
184+ if let Some ( comment_start) = comment_start
185+ && let Ok ( unsafe_line) = source_map. lookup_line ( item. span . lo ( ) )
186+ && let Ok ( comment_start_line) = source_map. lookup_line ( comment_start)
187+ && Lrc :: ptr_eq ( & unsafe_line. sf , & comment_start_line. sf )
188+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
189+ {
190+ comment_start_line. line < unsafe_line. line && text_has_safety_comment (
191+ src,
192+ & unsafe_line. sf . lines [ comment_start_line. line + 1 ..=unsafe_line. line ] ,
193+ unsafe_line. sf . start_pos . to_usize ( ) ,
194+ )
195+ } else {
196+ // Problem getting source text. Pretend a comment was found.
197+ true
198+ }
199+ } else {
200+ // No parent node. Pretend a comment was found.
201+ true
202+ }
203+ } else {
204+ false
205+ }
206+ }
207+
208+ fn comment_start_before_impl_in_mod (
209+ cx : & LateContext < ' _ > ,
210+ parent_mod : & hir:: Mod < ' _ > ,
211+ parent_mod_span : Span ,
212+ imple : & hir:: Item < ' _ > ,
213+ ) -> Option < BytePos > {
214+ parent_mod. item_ids . iter ( ) . enumerate ( ) . find_map ( |( idx, item_id) | {
215+ if * item_id == imple. item_id ( ) {
216+ if idx == 0 {
217+ // mod A { /* comment */ unsafe impl T {} ... }
218+ // ^------------------------------------------^ returns the start of this span
219+ // ^---------------------^ finally checks comments in this range
220+ if let Some ( sp) = walk_span_to_context ( parent_mod_span, SyntaxContext :: root ( ) ) {
221+ return Some ( sp. lo ( ) ) ;
222+ }
223+ } else {
224+ // some_item /* comment */ unsafe impl T {}
225+ // ^-------^ returns the end of this span
226+ // ^---------------^ finally checks comments in this range
227+ let prev_item = cx. tcx . hir ( ) . item ( parent_mod. item_ids [ idx - 1 ] ) ;
228+ if let Some ( sp) = walk_span_to_context ( prev_item. span , SyntaxContext :: root ( ) ) {
229+ return Some ( sp. hi ( ) ) ;
230+ }
231+ }
232+ }
233+ None
234+ } )
235+ }
236+
237+ fn span_from_macro_expansion_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> bool {
112238 let source_map = cx. sess ( ) . source_map ( ) ;
113- let ctxt = block. span . ctxt ( ) ;
114- if ctxt != SyntaxContext :: root ( ) {
115- // From a macro expansion. Get the text from the start of the macro declaration to start of the unsafe block.
239+ let ctxt = span. ctxt ( ) ;
240+ if ctxt == SyntaxContext :: root ( ) {
241+ false
242+ } else {
243+ // From a macro expansion. Get the text from the start of the macro declaration to start of the
244+ // unsafe block.
116245 // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
117246 // ^--------------------------------------------^
118- if let Ok ( unsafe_line) = source_map. lookup_line ( block . span . lo ( ) )
247+ if let Ok ( unsafe_line) = source_map. lookup_line ( span. lo ( ) )
119248 && let Ok ( macro_line) = source_map. lookup_line ( ctxt. outer_expn_data ( ) . def_site . lo ( ) )
120249 && Lrc :: ptr_eq ( & unsafe_line. sf , & macro_line. sf )
121250 && let Some ( src) = unsafe_line. sf . src . as_deref ( )
@@ -129,24 +258,35 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
129258 // Problem getting source text. Pretend a comment was found.
130259 true
131260 }
132- } else if let Ok ( unsafe_line) = source_map. lookup_line ( block. span . lo ( ) )
261+ }
262+ }
263+
264+ fn span_in_body_has_safety_comment ( cx : & LateContext < ' _ > , span : Span ) -> bool {
265+ let source_map = cx. sess ( ) . source_map ( ) ;
266+ let ctxt = span. ctxt ( ) ;
267+ if ctxt == SyntaxContext :: root ( )
133268 && let Some ( body) = cx. enclosing_body
134- && let Some ( body_span) = walk_span_to_context ( cx. tcx . hir ( ) . body ( body) . value . span , SyntaxContext :: root ( ) )
135- && let Ok ( body_line) = source_map. lookup_line ( body_span. lo ( ) )
136- && Lrc :: ptr_eq ( & unsafe_line. sf , & body_line. sf )
137- && let Some ( src) = unsafe_line. sf . src . as_deref ( )
138269 {
139- // Get the text from the start of function body to the unsafe block.
140- // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
141- // ^-------------^
142- body_line. line < unsafe_line. line && text_has_safety_comment (
143- src,
144- & unsafe_line. sf . lines [ body_line. line + 1 ..=unsafe_line. line ] ,
145- unsafe_line. sf . start_pos . to_usize ( ) ,
146- )
270+ if let Ok ( unsafe_line) = source_map. lookup_line ( span. lo ( ) )
271+ && let Some ( body_span) = walk_span_to_context ( cx. tcx . hir ( ) . body ( body) . value . span , SyntaxContext :: root ( ) )
272+ && let Ok ( body_line) = source_map. lookup_line ( body_span. lo ( ) )
273+ && Lrc :: ptr_eq ( & unsafe_line. sf , & body_line. sf )
274+ && let Some ( src) = unsafe_line. sf . src . as_deref ( )
275+ {
276+ // Get the text from the start of function body to the unsafe block.
277+ // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
278+ // ^-------------^
279+ body_line. line < unsafe_line. line && text_has_safety_comment (
280+ src,
281+ & unsafe_line. sf . lines [ body_line. line + 1 ..=unsafe_line. line ] ,
282+ unsafe_line. sf . start_pos . to_usize ( ) ,
283+ )
284+ } else {
285+ // Problem getting source text. Pretend a comment was found.
286+ true
287+ }
147288 } else {
148- // Problem getting source text. Pretend a comment was found.
149- true
289+ false
150290 }
151291}
152292
0 commit comments