Skip to content

Commit e498f85

Browse files
committed
Support more expressions in Locals, check for an unaltered placeholder
1 parent 564fe48 commit e498f85

File tree

3 files changed

+302
-64
lines changed

3 files changed

+302
-64
lines changed
Lines changed: 128 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
22
use clippy_utils::source::{snippet, indent_of, reindent_multiline};
33
use rustc_errors::Applicability;
4-
use rustc_hir::{Block, BlockCheckMode, ExprKind, HirId, Local, UnsafeSource};
4+
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, MatchSource, UnsafeSource, YieldSource};
55
use rustc_lexer::TokenKind;
66
use rustc_lint::{LateContext, LateLintPass};
77
use rustc_middle::lint::in_external_macro;
88
use rustc_middle::ty::TyCtxt;
99
use rustc_session::{impl_lint_pass, declare_tool_lint};
10-
use rustc_span::Span;
10+
use rustc_span::{BytePos, Span};
1111
use std::borrow::Cow;
1212
use clippy_utils::is_lint_allowed;
1313

@@ -46,23 +46,25 @@ impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
4646

4747
#[derive(Default)]
4848
pub struct UndocumentedUnsafeBlocks {
49-
pub local: bool
49+
pub local_level: u32,
50+
}
51+
52+
enum ErrorKind {
53+
NoComment,
54+
Placeholder
5055
}
5156

5257
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
5358
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
54-
if self.local {
55-
self.local = false;
56-
return
57-
}
58-
59-
if_chain! {
60-
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
61-
if !in_external_macro(cx.tcx.sess, block.span);
62-
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
63-
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
64-
then {
65-
find_candidate(cx, block.span, enclosing_scope_hir_id)
59+
if self.local_level == 0 {
60+
if_chain! {
61+
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
62+
if !in_external_macro(cx.tcx.sess, block.span);
63+
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
64+
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
65+
then {
66+
find_candidate(cx, block.span, enclosing_scope_hir_id)
67+
}
6668
}
6769
}
6870
}
@@ -72,71 +74,149 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
7274
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
7375
if !in_external_macro(cx.tcx.sess, local.span);
7476
if let Some(init) = local.init;
75-
if let ExprKind::Block(block, _) = init.kind;
77+
if let Some((block, element_count)) = find_block_in_expr(&init.kind);
7678
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
7779
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
7880
then {
79-
self.local = true;
81+
self.local_level = self.local_level.saturating_add(element_count);
82+
8083
find_candidate(cx, local.span, enclosing_scope_hir_id)
8184
}
8285
}
8386
}
87+
88+
fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
89+
self.local_level = self.local_level.saturating_sub(1);
90+
}
8491
}
8592

86-
fn find_candidate(cx: &LateContext<'_>, span: Span, enclosing_hir_id: HirId) {
87-
if let Some(true) = block_has_safety_comment(cx.tcx, enclosing_hir_id, span) {
88-
return;
93+
fn find_block_in_expr(expr_kind: &ExprKind<'hir>) -> Option<(&'tcx Block<'hir>, u32)> {
94+
match expr_kind {
95+
ExprKind::Array(elements) | ExprKind::Tup(elements) => {
96+
let unsafe_blocks = elements.iter().filter_map(|e|
97+
match e {
98+
Expr{
99+
kind: ExprKind::Block(block @ Block {
100+
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
101+
..
102+
}, _),
103+
..
104+
} => Some(block),
105+
_ => None
106+
}).collect::<Vec<_>>();
107+
108+
if let Some(block) = unsafe_blocks.get(0) {
109+
return Some((block, unsafe_blocks.len().try_into().unwrap()))
110+
}
111+
112+
None
113+
},
114+
ExprKind::Box(Expr {
115+
kind: ExprKind::Block(block, _),
116+
..
117+
}) |
118+
ExprKind::Unary(_, Expr {
119+
kind: ExprKind::Block(block, _),
120+
..
121+
}) | ExprKind::Match(Expr {
122+
kind: ExprKind::Block(block, _),
123+
..
124+
}, _, MatchSource::Normal) | ExprKind::Loop(block, _, _, _) | ExprKind::Block(block, _) | ExprKind::AddrOf(_, _, Expr {
125+
kind: ExprKind::Block(block, _),
126+
..
127+
}) | ExprKind::Break(_, Some(Expr {
128+
kind: ExprKind::Block(block, _),
129+
..
130+
})) | ExprKind::Ret(Some(Expr {
131+
kind: ExprKind::Block(block, _),
132+
..
133+
})) | ExprKind::Repeat(Expr {
134+
kind: ExprKind::Block(block, _),
135+
..
136+
}, _) | ExprKind::Yield(Expr{
137+
kind: ExprKind::Block(block, _),
138+
..
139+
}, YieldSource::Yield) => Some((block, 1)),
140+
_ => None
89141
}
142+
}
90143

91-
let block_indent = indent_of(cx, span);
92-
let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
93-
94-
span_lint_and_sugg(
95-
cx,
96-
UNDOCUMENTED_UNSAFE_BLOCKS,
97-
span,
98-
"unsafe block missing a safety comment",
99-
"consider adding a safety comment",
100-
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
101-
Applicability::HasPlaceholders,
102-
);
144+
fn find_candidate(cx: &LateContext<'_>, span: Span, enclosing_hir_id: HirId) {
145+
if let Some(error_kind) = block_has_safety_comment(cx.tcx, enclosing_hir_id, span) {
146+
match error_kind {
147+
ErrorKind::NoComment => {
148+
let block_indent = indent_of(cx, span);
149+
let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
150+
151+
span_lint_and_sugg(
152+
cx,
153+
UNDOCUMENTED_UNSAFE_BLOCKS,
154+
span,
155+
"unsafe block missing a safety comment",
156+
"consider adding a safety comment",
157+
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
158+
Applicability::HasPlaceholders,
159+
);
160+
}
161+
ErrorKind::Placeholder => {
162+
span_lint_and_help(
163+
cx,
164+
UNDOCUMENTED_UNSAFE_BLOCKS,
165+
span,
166+
"unsafe block has safety comment placeholder",
167+
None,
168+
"consider replacing the placeholder with a safety comment"
169+
);
170+
}
171+
}
172+
}
103173
}
104174

105-
fn block_has_safety_comment(tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
175+
fn block_has_safety_comment(tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<ErrorKind> {
106176
let map = tcx.hir();
107177
let source_map = tcx.sess.source_map();
108178

109179
let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
110-
let between_span = enclosing_scope_span.until(block_span);
180+
let between_span = enclosing_scope_span.to(block_span);
111181

112182
let file_name = source_map.span_to_filename(between_span);
113183
let source_file = source_map.get_source_file(&file_name)?;
114184

115185
let lex_start = (between_span.lo().0 + 1) as usize;
116-
let mut src_str = source_file.src.as_ref()?[lex_start..between_span.hi().0 as usize].to_string();
117-
118-
src_str.retain(|c| !c.is_whitespace());
119-
120-
let src_str_split = src_str.lines().rev().collect::<Vec<_>>();
121-
let src_str = src_str_split.join("");
186+
let src_str = source_file.src.as_ref()?[lex_start..between_span.hi().0 as usize].to_string();
122187

123188
let mut pos = 0;
124-
let mut found_safety_comment = false;
189+
let mut comment = false;
125190

126191
for token in rustc_lexer::tokenize(&src_str) {
127192
match token.kind {
128193
TokenKind::LineComment { doc_style: None }
129-
| TokenKind::BlockComment { doc_style: None, terminated: true } => {
130-
if src_str[pos + 2.. pos + token.len].to_ascii_uppercase().contains("SAFETY:") {
131-
found_safety_comment = true;
132-
break
194+
| TokenKind::BlockComment { doc_style: None, terminated: true } => {
195+
let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
196+
197+
if comment_str.contains("SAFETY: ...") {
198+
return Some(ErrorKind::Placeholder);
199+
} else if comment_str.contains("SAFETY:") {
200+
comment = true;
201+
}
202+
},
203+
TokenKind::Whitespace => {},
204+
_ => {
205+
if comment {
206+
let comment_line_num = source_file.lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap())).0;
207+
let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
208+
209+
if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
210+
return None;
211+
}
212+
213+
comment = false;
133214
}
134215
},
135-
_ => break
136216
}
137217

138218
pos += token.len;
139219
}
140220

141-
Some(found_safety_comment)
221+
Some(ErrorKind::NoComment)
142222
}

tests/ui/undocumented_unsafe_blocks.rs

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,57 @@
22

33
// Valid comments
44

5+
fn nested_local() {
6+
let _ = {
7+
let _ = {
8+
// Safety:
9+
let _ = unsafe {};
10+
};
11+
};
12+
}
13+
14+
fn deep_nest() {
15+
let _ = {
16+
let _ = {
17+
// Safety:
18+
let _ = unsafe {};
19+
20+
// Safety:
21+
unsafe {};
22+
23+
let _ = {
24+
let _ = {
25+
let _ = {
26+
let _ = {
27+
let _ = {
28+
// Safety:
29+
let _ = unsafe {};
30+
31+
// Safety:
32+
unsafe {};
33+
};
34+
};
35+
};
36+
37+
// Safety:
38+
unsafe {};
39+
};
40+
};
41+
};
42+
43+
// Safety:
44+
unsafe {};
45+
};
46+
47+
// Safety:
48+
unsafe {};
49+
}
50+
51+
fn local_tuple_expression() {
52+
// Safety:
53+
let _ = (42, unsafe {});
54+
}
55+
556
fn line_comment() {
657
// Safety:
758
unsafe {}
@@ -75,15 +126,6 @@ fn safety_with_prepended_text() {
75126
unsafe {}
76127
}
77128

78-
fn nested_local() {
79-
let _ = {
80-
let _ = {
81-
// Safety:
82-
let _ = unsafe {};
83-
};
84-
};
85-
}
86-
87129
fn local_line_comment() {
88130
// Safety:
89131
let _ = unsafe {};
@@ -100,6 +142,38 @@ fn no_comment() {
100142
unsafe {}
101143
}
102144

145+
fn contains_placeholder() {
146+
// Safety: ...
147+
unsafe {};
148+
}
149+
150+
fn no_comment_array() {
151+
let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
152+
}
153+
154+
fn no_comment_tuple() {
155+
let _ = (42, unsafe {}, "test", unsafe {});
156+
}
157+
158+
fn no_comment_unary() {
159+
let _ = *unsafe { &42 };
160+
}
161+
162+
#[allow(clippy::match_single_binding)]
163+
fn no_comment_match() {
164+
let _ = match unsafe {} {
165+
_ => {},
166+
};
167+
}
168+
169+
fn no_comment_addr_of() {
170+
let _ = &unsafe {};
171+
}
172+
173+
fn no_comment_repeat() {
174+
let _ = [unsafe {}; 5];
175+
}
176+
103177
fn local_no_comment() {
104178
let _ = unsafe {};
105179
}

0 commit comments

Comments
 (0)