Skip to content

Commit 4fa5757

Browse files
committed
Lint unnecessary safety comments on statements and block tail expressions
1 parent b8c3f64 commit 4fa5757

File tree

4 files changed

+220
-9
lines changed

4 files changed

+220
-9
lines changed

clippy_lints/src/undocumented_unsafe_blocks.rs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_help;
24
use clippy_utils::source::walk_span_to_context;
5+
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
36
use clippy_utils::{get_parent_node, is_lint_allowed};
7+
use hir::HirId;
48
use rustc_data_structures::sync::Lrc;
59
use rustc_hir as hir;
610
use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
@@ -90,8 +94,8 @@ declare_clippy_lint! {
9094

9195
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS, UNNECESSARY_SAFETY_COMMENT]);
9296

93-
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
94-
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
97+
impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
98+
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
9599
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
96100
&& !in_external_macro(cx.tcx.sess, block.span)
97101
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
@@ -115,6 +119,45 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
115119
"consider adding a safety comment on the preceding line",
116120
);
117121
}
122+
123+
if let Some(tail) = block.expr
124+
&& !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
125+
&& !in_external_macro(cx.tcx.sess, tail.span)
126+
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id)
127+
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
128+
{
129+
span_lint_and_help(
130+
cx,
131+
UNNECESSARY_SAFETY_COMMENT,
132+
tail.span,
133+
"expression has unnecessary safety comment",
134+
Some(help_span),
135+
"consider removing the safety comment",
136+
);
137+
}
138+
}
139+
140+
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &hir::Stmt<'tcx>) {
141+
let expr = match stmt.kind {
142+
hir::StmtKind::Local(&hir::Local { init: Some(expr), .. })
143+
| hir::StmtKind::Expr(expr)
144+
| hir::StmtKind::Semi(expr) => expr,
145+
_ => return,
146+
};
147+
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
148+
&& !in_external_macro(cx.tcx.sess, stmt.span)
149+
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id)
150+
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
151+
{
152+
span_lint_and_help(
153+
cx,
154+
UNNECESSARY_SAFETY_COMMENT,
155+
stmt.span,
156+
"statement has unnecessary safety comment",
157+
Some(help_span),
158+
"consider removing the safety comment",
159+
);
160+
}
118161
}
119162

120163
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
@@ -216,6 +259,36 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
216259
}
217260
}
218261

262+
fn expr_has_unnecessary_safety_comment<'tcx>(
263+
cx: &LateContext<'tcx>,
264+
expr: &'tcx hir::Expr<'tcx>,
265+
comment_pos: BytePos,
266+
) -> Option<Span> {
267+
// this should roughly be the reverse of `block_parents_have_safety_comment`
268+
if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
269+
hir::ExprKind::Block(
270+
Block {
271+
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
272+
..
273+
},
274+
_,
275+
) => ControlFlow::Break(()),
276+
// statements will be handled by check_stmt itself again
277+
hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
278+
_ => ControlFlow::Continue(Descend::Yes),
279+
})
280+
.is_some()
281+
{
282+
return None;
283+
}
284+
285+
let source_map = cx.tcx.sess.source_map();
286+
let span = Span::new(comment_pos, comment_pos, SyntaxContext::root(), None);
287+
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
288+
289+
Some(help_span)
290+
}
291+
219292
fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
220293
let source_map = cx.sess().source_map();
221294
let file_pos = source_map.lookup_byte_offset(span.lo());
@@ -358,6 +431,55 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
358431
}
359432
}
360433

434+
/// Checks if the lines immediately preceding the item contain a safety comment.
435+
#[allow(clippy::collapsible_match)]
436+
fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
437+
match span_from_macro_expansion_has_safety_comment(cx, span) {
438+
HasSafetyComment::Maybe => (),
439+
has_safety_comment => return has_safety_comment,
440+
}
441+
442+
if span.ctxt() == SyntaxContext::root() {
443+
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
444+
let comment_start = match parent_node {
445+
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
446+
_ => return HasSafetyComment::Maybe,
447+
};
448+
449+
let source_map = cx.sess().source_map();
450+
if let Some(comment_start) = comment_start
451+
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
452+
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
453+
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
454+
&& let Some(src) = unsafe_line.sf.src.as_deref()
455+
{
456+
unsafe_line.sf.lines(|lines| {
457+
if comment_start_line.line >= unsafe_line.line {
458+
HasSafetyComment::No
459+
} else {
460+
match text_has_safety_comment(
461+
src,
462+
&lines[comment_start_line.line + 1..=unsafe_line.line],
463+
unsafe_line.sf.start_pos.to_usize(),
464+
) {
465+
Some(b) => HasSafetyComment::Yes(b),
466+
None => HasSafetyComment::No,
467+
}
468+
}
469+
})
470+
} else {
471+
// Problem getting source text. Pretend a comment was found.
472+
HasSafetyComment::Maybe
473+
}
474+
} else {
475+
// No parent node. Pretend a comment was found.
476+
HasSafetyComment::Maybe
477+
}
478+
} else {
479+
HasSafetyComment::No
480+
}
481+
}
482+
361483
fn comment_start_before_item_in_mod(
362484
cx: &LateContext<'_>,
363485
parent_mod: &hir::Mod<'_>,

clippy_utils/src/visitors.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,36 +170,36 @@ where
170170
cb: F,
171171
}
172172

173-
struct WithStmtGuarg<'a, F> {
173+
struct WithStmtGuard<'a, F> {
174174
val: &'a mut RetFinder<F>,
175175
prev_in_stmt: bool,
176176
}
177177

178178
impl<F> RetFinder<F> {
179-
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
179+
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuard<'_, F> {
180180
let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
181-
WithStmtGuarg {
181+
WithStmtGuard {
182182
val: self,
183183
prev_in_stmt,
184184
}
185185
}
186186
}
187187

188-
impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
188+
impl<F> std::ops::Deref for WithStmtGuard<'_, F> {
189189
type Target = RetFinder<F>;
190190

191191
fn deref(&self) -> &Self::Target {
192192
self.val
193193
}
194194
}
195195

196-
impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
196+
impl<F> std::ops::DerefMut for WithStmtGuard<'_, F> {
197197
fn deref_mut(&mut self) -> &mut Self::Target {
198198
self.val
199199
}
200200
}
201201

202-
impl<F> Drop for WithStmtGuarg<'_, F> {
202+
impl<F> Drop for WithStmtGuard<'_, F> {
203203
fn drop(&mut self) {
204204
self.val.in_stmt = self.prev_in_stmt;
205205
}

tests/ui/undocumented_unsafe_blocks.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,4 +522,35 @@ fn issue_9142() {
522522
};
523523
}
524524

525+
mod unnecessary_from_macro {
526+
trait T {}
527+
528+
macro_rules! no_safety_comment {
529+
($t:ty) => {
530+
impl T for $t {}
531+
};
532+
}
533+
534+
// FIXME: This is not caught
535+
// Safety: unnecessary
536+
no_safety_comment!(());
537+
538+
macro_rules! with_safety_comment {
539+
($t:ty) => {
540+
// Safety: unnecessary
541+
impl T for $t {}
542+
};
543+
}
544+
545+
with_safety_comment!(i32);
546+
}
547+
548+
fn unnecessary_on_stmt_and_expr() -> u32 {
549+
// SAFETY: unnecessary
550+
let num = 42;
551+
552+
// SAFETY: unnecessary
553+
24
554+
}
555+
525556
fn main() {}

tests/ui/undocumented_unsafe_blocks.stderr

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,24 @@ LL | unsafe {};
344344
|
345345
= help: consider adding a safety comment on the preceding line
346346

347+
error: statement has unnecessary safety comment
348+
--> $DIR/undocumented_unsafe_blocks.rs:514:5
349+
|
350+
LL | / let _ = {
351+
LL | | if unsafe { true } {
352+
LL | | todo!();
353+
LL | | } else {
354+
... |
355+
LL | | }
356+
LL | | };
357+
| |______^
358+
|
359+
help: consider removing the safety comment
360+
--> $DIR/undocumented_unsafe_blocks.rs:513:5
361+
|
362+
LL | // SAFETY: this is more than one level away, so it should warn
363+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
364+
347365
error: unsafe block missing a safety comment
348366
--> $DIR/undocumented_unsafe_blocks.rs:515:12
349367
|
@@ -360,5 +378,45 @@ LL | let bar = unsafe {};
360378
|
361379
= help: consider adding a safety comment on the preceding line
362380

363-
error: aborting due to 40 previous errors
381+
error: impl has unnecessary safety comment
382+
--> $DIR/undocumented_unsafe_blocks.rs:541:13
383+
|
384+
LL | impl T for $t {}
385+
| ^^^^^^^^^^^^^^^^
386+
...
387+
LL | with_safety_comment!(i32);
388+
| ------------------------- in this macro invocation
389+
|
390+
help: consider removing the safety comment
391+
--> $DIR/undocumented_unsafe_blocks.rs:540:13
392+
|
393+
LL | // Safety: unnecessary
394+
| ^^^^^^^^^^^^^^^^^^^^^^
395+
= note: this error originates in the macro `with_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
396+
397+
error: expression has unnecessary safety comment
398+
--> $DIR/undocumented_unsafe_blocks.rs:553:5
399+
|
400+
LL | 24
401+
| ^^
402+
|
403+
help: consider removing the safety comment
404+
--> $DIR/undocumented_unsafe_blocks.rs:552:5
405+
|
406+
LL | // SAFETY: unnecessary
407+
| ^^^^^^^^^^^^^^^^^^^^^^
408+
409+
error: statement has unnecessary safety comment
410+
--> $DIR/undocumented_unsafe_blocks.rs:550:5
411+
|
412+
LL | let num = 42;
413+
| ^^^^^^^^^^^^^
414+
|
415+
help: consider removing the safety comment
416+
--> $DIR/undocumented_unsafe_blocks.rs:549:5
417+
|
418+
LL | // SAFETY: unnecessary
419+
| ^^^^^^^^^^^^^^^^^^^^^^
420+
421+
error: aborting due to 44 previous errors
364422

0 commit comments

Comments
 (0)