1
1
use clippy_utils:: diagnostics:: span_lint_and_help;
2
- use clippy_utils:: is_lint_allowed;
3
2
use clippy_utils:: source:: walk_span_to_context;
3
+ use clippy_utils:: { get_parent_node, is_lint_allowed} ;
4
4
use 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 } ;
6
7
use rustc_lexer:: { tokenize, TokenKind } ;
7
8
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
8
9
use rustc_middle:: lint:: in_external_macro;
9
10
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
10
- use rustc_span:: { BytePos , Pos , SyntaxContext } ;
11
+ use rustc_span:: { BytePos , Pos , Span , SyntaxContext } ;
11
12
12
13
declare_clippy_lint ! {
13
14
/// ### What it does
14
- /// Checks for `unsafe` blocks without a `// SAFETY: ` comment
15
+ /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
15
16
/// explaining why the unsafe operations performed inside
16
17
/// the block are safe.
17
18
///
@@ -34,7 +35,7 @@ declare_clippy_lint! {
34
35
/// ```
35
36
///
36
37
/// ### Why is this bad?
37
- /// Undocumented unsafe blocks can make it difficult to
38
+ /// Undocumented unsafe blocks and impls can make it difficult to
38
39
/// read and maintain code, as well as uncover unsoundness
39
40
/// and bugs.
40
41
///
@@ -66,7 +67,7 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
66
67
if block. rules == BlockCheckMode :: UnsafeBlock ( UnsafeSource :: UserProvided )
67
68
&& !in_external_macro ( cx. tcx . sess , block. span )
68
69
&& !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 )
70
71
&& !block_has_safety_comment ( cx, block)
71
72
{
72
73
let source_map = cx. tcx . sess . source_map ( ) ;
@@ -86,11 +87,37 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
86
87
) ;
87
88
}
88
89
}
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
+ }
89
116
}
90
117
91
- fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
118
+ fn is_unsafe_from_proc_macro ( cx : & LateContext < ' _ > , span : Span ) -> bool {
92
119
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 ( ) ) ;
94
121
file_pos
95
122
. sf
96
123
. src
@@ -100,7 +127,7 @@ fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
100
127
}
101
128
102
129
/// 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 {
104
131
// This intentionally ignores text before the start of a function so something like:
105
132
// ```
106
133
// // SAFETY: reason
@@ -109,13 +136,115 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
109
136
// won't work. This is to avoid dealing with where such a comment should be place relative to
110
137
// attributes and doc comments.
111
138
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 {
112
238
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.
116
245
// macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
117
246
// ^--------------------------------------------^
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 ( ) )
119
248
&& let Ok ( macro_line) = source_map. lookup_line ( ctxt. outer_expn_data ( ) . def_site . lo ( ) )
120
249
&& Lrc :: ptr_eq ( & unsafe_line. sf , & macro_line. sf )
121
250
&& let Some ( src) = unsafe_line. sf . src . as_deref ( )
@@ -129,24 +258,35 @@ fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
129
258
// Problem getting source text. Pretend a comment was found.
130
259
true
131
260
}
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 ( )
133
268
&& 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 ( )
138
269
{
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
+ }
147
288
} else {
148
- // Problem getting source text. Pretend a comment was found.
149
- true
289
+ false
150
290
}
151
291
}
152
292
0 commit comments