Skip to content

Commit a1632ff

Browse files
committed
Auto merge of rust-lang#8761 - tamaroning:fix_8505, r=Jarcho
`undocumented_unsafe_blocks` does not trigger on unsafe trait impls Closes rust-lang#8505 changelog: This lint checks unsafe impls NOT from macro expansions and checks ones in macro declarations. ~~`unsafe impl`s from macro invocations don't trigger the lint for now.~~ ~~This lint checks unsafe impls from/not from macro expansions~~
2 parents 6ec7359 + 9e66f2d commit a1632ff

File tree

3 files changed

+437
-30
lines changed

3 files changed

+437
-30
lines changed

clippy_lints/src/undocumented_unsafe_blocks.rs

+168-28
Original file line numberDiff line numberDiff line change
@@ -1,17 +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;
5-
use rustc_hir::{Block, BlockCheckMode, UnsafeSource};
5+
use rustc_hir as hir;
6+
use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
67
use rustc_lexer::{tokenize, TokenKind};
78
use rustc_lint::{LateContext, LateLintPass, LintContext};
89
use rustc_middle::lint::in_external_macro;
910
use rustc_session::{declare_lint_pass, declare_tool_lint};
10-
use rustc_span::{BytePos, Pos, SyntaxContext};
11+
use rustc_span::{BytePos, Pos, Span, SyntaxContext};
1112

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

Comments
 (0)