Skip to content

Commit 8d85889

Browse files
committed
fix
1 parent 5d0ca74 commit 8d85889

File tree

3 files changed

+161
-94
lines changed

3 files changed

+161
-94
lines changed

clippy_lints/src/undocumented_unsafe_blocks.rs

Lines changed: 132 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::is_lint_allowed;
32
use clippy_utils::source::walk_span_to_context;
3+
use clippy_utils::{get_parent_node, is_lint_allowed};
44
use rustc_data_structures::sync::Lrc;
55
use rustc_hir as hir;
6-
use rustc_hir::{Block, BlockCheckMode, UnsafeSource};
6+
use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
77
use rustc_lexer::{tokenize, TokenKind};
88
use rustc_lint::{LateContext, LateLintPass, LintContext};
99
use rustc_middle::lint::in_external_macro;
1010
use rustc_session::{declare_lint_pass, declare_tool_lint};
1111
use rustc_span::{BytePos, Pos, Span, SyntaxContext};
12-
use std::rc::Rc;
1312

1413
declare_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

tests/ui/undocumented_unsafe_blocks.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,22 @@ mod unsafe_impl_smoke_test {
363363
mod unsafe_impl_from_macro {
364364
unsafe trait T {}
365365

366-
macro_rules! unsafe_impl {
366+
macro_rules! no_safety_comment {
367367
($t:ty) => {
368368
unsafe impl T for $t {}
369369
};
370370
}
371-
// ok: from macro expanision
372-
unsafe_impl!(());
373-
// ok: from macro expansion
374-
unsafe_impl!(i32);
371+
// error
372+
no_safety_comment!(());
373+
374+
macro_rules! with_safety_comment {
375+
($t:ty) => {
376+
// SAFETY:
377+
unsafe impl T for $t {}
378+
};
379+
}
380+
// ok
381+
with_safety_comment!((i32));
375382
}
376383

377384
#[rustfmt::skip]

tests/ui/undocumented_unsafe_blocks.stderr

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,36 +164,48 @@ LL | unsafe impl B for (u32) {}
164164
= help: consider adding a safety comment on the preceding line
165165

166166
error: unsafe impl missing a safety comment
167-
--> $DIR/undocumented_unsafe_blocks.rs:420:5
167+
--> $DIR/undocumented_unsafe_blocks.rs:368:13
168+
|
169+
LL | unsafe impl T for $t {}
170+
| ^^^^^^^^^^^^^^^^^^^^^^^
171+
...
172+
LL | no_safety_comment!(());
173+
| ---------------------- in this macro invocation
174+
|
175+
= help: consider adding a safety comment on the preceding line
176+
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
177+
178+
error: unsafe impl missing a safety comment
179+
--> $DIR/undocumented_unsafe_blocks.rs:427:5
168180
|
169181
LL | unsafe impl NoComment for () {}
170182
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
171183
|
172184
= help: consider adding a safety comment on the preceding line
173185

174186
error: unsafe impl missing a safety comment
175-
--> $DIR/undocumented_unsafe_blocks.rs:424:19
187+
--> $DIR/undocumented_unsafe_blocks.rs:431:19
176188
|
177189
LL | /* SAFETY: */ unsafe impl InlineComment for () {}
178190
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
179191
|
180192
= help: consider adding a safety comment on the preceding line
181193

182194
error: unsafe impl missing a safety comment
183-
--> $DIR/undocumented_unsafe_blocks.rs:428:5
195+
--> $DIR/undocumented_unsafe_blocks.rs:435:5
184196
|
185197
LL | unsafe impl TrailingComment for () {} // SAFETY:
186198
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
187199
|
188200
= help: consider adding a safety comment on the preceding line
189201

190202
error: unsafe impl missing a safety comment
191-
--> $DIR/undocumented_unsafe_blocks.rs:433:5
203+
--> $DIR/undocumented_unsafe_blocks.rs:440:5
192204
|
193205
LL | unsafe impl Interference for () {}
194206
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
195207
|
196208
= help: consider adding a safety comment on the preceding line
197209

198-
error: aborting due to 24 previous errors
210+
error: aborting due to 25 previous errors
199211

0 commit comments

Comments
 (0)