Lint unnecessary safety comments on statements and block tail expressions

This commit is contained in:
Lukas Wirth 2022-11-17 15:14:03 +01:00
parent b8c3f64cee
commit 4fa5757530
4 changed files with 220 additions and 9 deletions

View file

@ -1,6 +1,10 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::walk_span_to_context;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{get_parent_node, is_lint_allowed};
use hir::HirId;
use rustc_data_structures::sync::Lrc;
use rustc_hir as hir;
use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
@ -90,8 +94,8 @@ declare_clippy_lint! {
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS, UNNECESSARY_SAFETY_COMMENT]);
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
&& !in_external_macro(cx.tcx.sess, block.span)
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
@ -115,6 +119,45 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
"consider adding a safety comment on the preceding line",
);
}
if let Some(tail) = block.expr
&& !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
&& !in_external_macro(cx.tcx.sess, tail.span)
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id)
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
{
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
tail.span,
"expression has unnecessary safety comment",
Some(help_span),
"consider removing the safety comment",
);
}
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &hir::Stmt<'tcx>) {
let expr = match stmt.kind {
hir::StmtKind::Local(&hir::Local { init: Some(expr), .. })
| hir::StmtKind::Expr(expr)
| hir::StmtKind::Semi(expr) => expr,
_ => return,
};
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
&& !in_external_macro(cx.tcx.sess, stmt.span)
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id)
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
{
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
stmt.span,
"statement has unnecessary safety comment",
Some(help_span),
"consider removing the safety comment",
);
}
}
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
@ -216,6 +259,36 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
}
}
fn expr_has_unnecessary_safety_comment<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
comment_pos: BytePos,
) -> Option<Span> {
// this should roughly be the reverse of `block_parents_have_safety_comment`
if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
hir::ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
},
_,
) => ControlFlow::Break(()),
// statements will be handled by check_stmt itself again
hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
_ => ControlFlow::Continue(Descend::Yes),
})
.is_some()
{
return None;
}
let source_map = cx.tcx.sess.source_map();
let span = Span::new(comment_pos, comment_pos, SyntaxContext::root(), None);
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
Some(help_span)
}
fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
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
}
}
/// Checks if the lines immediately preceding the item contain a safety comment.
#[allow(clippy::collapsible_match)]
fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
match span_from_macro_expansion_has_safety_comment(cx, span) {
HasSafetyComment::Maybe => (),
has_safety_comment => return has_safety_comment,
}
if span.ctxt() == SyntaxContext::root() {
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
let comment_start = match parent_node {
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
_ => return HasSafetyComment::Maybe,
};
let source_map = cx.sess().source_map();
if let Some(comment_start) = comment_start
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
unsafe_line.sf.lines(|lines| {
if comment_start_line.line >= unsafe_line.line {
HasSafetyComment::No
} else {
match text_has_safety_comment(
src,
&lines[comment_start_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
) {
Some(b) => HasSafetyComment::Yes(b),
None => HasSafetyComment::No,
}
}
})
} else {
// Problem getting source text. Pretend a comment was found.
HasSafetyComment::Maybe
}
} else {
// No parent node. Pretend a comment was found.
HasSafetyComment::Maybe
}
} else {
HasSafetyComment::No
}
}
fn comment_start_before_item_in_mod(
cx: &LateContext<'_>,
parent_mod: &hir::Mod<'_>,

View file

@ -170,22 +170,22 @@ where
cb: F,
}
struct WithStmtGuarg<'a, F> {
struct WithStmtGuard<'a, F> {
val: &'a mut RetFinder<F>,
prev_in_stmt: bool,
}
impl<F> RetFinder<F> {
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuard<'_, F> {
let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
WithStmtGuarg {
WithStmtGuard {
val: self,
prev_in_stmt,
}
}
}
impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
impl<F> std::ops::Deref for WithStmtGuard<'_, F> {
type Target = RetFinder<F>;
fn deref(&self) -> &Self::Target {
@ -193,13 +193,13 @@ where
}
}
impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
impl<F> std::ops::DerefMut for WithStmtGuard<'_, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.val
}
}
impl<F> Drop for WithStmtGuarg<'_, F> {
impl<F> Drop for WithStmtGuard<'_, F> {
fn drop(&mut self) {
self.val.in_stmt = self.prev_in_stmt;
}

View file

@ -522,4 +522,35 @@ fn issue_9142() {
};
}
mod unnecessary_from_macro {
trait T {}
macro_rules! no_safety_comment {
($t:ty) => {
impl T for $t {}
};
}
// FIXME: This is not caught
// Safety: unnecessary
no_safety_comment!(());
macro_rules! with_safety_comment {
($t:ty) => {
// Safety: unnecessary
impl T for $t {}
};
}
with_safety_comment!(i32);
}
fn unnecessary_on_stmt_and_expr() -> u32 {
// SAFETY: unnecessary
let num = 42;
// SAFETY: unnecessary
24
}
fn main() {}

View file

@ -344,6 +344,24 @@ LL | unsafe {};
|
= help: consider adding a safety comment on the preceding line
error: statement has unnecessary safety comment
--> $DIR/undocumented_unsafe_blocks.rs:514:5
|
LL | / let _ = {
LL | | if unsafe { true } {
LL | | todo!();
LL | | } else {
... |
LL | | }
LL | | };
| |______^
|
help: consider removing the safety comment
--> $DIR/undocumented_unsafe_blocks.rs:513:5
|
LL | // SAFETY: this is more than one level away, so it should warn
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unsafe block missing a safety comment
--> $DIR/undocumented_unsafe_blocks.rs:515:12
|
@ -360,5 +378,45 @@ LL | let bar = unsafe {};
|
= help: consider adding a safety comment on the preceding line
error: aborting due to 40 previous errors
error: impl has unnecessary safety comment
--> $DIR/undocumented_unsafe_blocks.rs:541:13
|
LL | impl T for $t {}
| ^^^^^^^^^^^^^^^^
...
LL | with_safety_comment!(i32);
| ------------------------- in this macro invocation
|
help: consider removing the safety comment
--> $DIR/undocumented_unsafe_blocks.rs:540:13
|
LL | // Safety: unnecessary
| ^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `with_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expression has unnecessary safety comment
--> $DIR/undocumented_unsafe_blocks.rs:553:5
|
LL | 24
| ^^
|
help: consider removing the safety comment
--> $DIR/undocumented_unsafe_blocks.rs:552:5
|
LL | // SAFETY: unnecessary
| ^^^^^^^^^^^^^^^^^^^^^^
error: statement has unnecessary safety comment
--> $DIR/undocumented_unsafe_blocks.rs:550:5
|
LL | let num = 42;
| ^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> $DIR/undocumented_unsafe_blocks.rs:549:5
|
LL | // SAFETY: unnecessary
| ^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 44 previous errors