refactor: lint man. instant elapsed and unch. dur. subtr. in single pass

This commit is contained in:
Nadir Fejzic 2022-11-07 20:58:49 +01:00
parent b485832b16
commit 2f2eb2e4ba
3 changed files with 71 additions and 81 deletions

View File

@ -1,17 +1,48 @@
use clippy_utils::{diagnostics, meets_msrv, msrvs, source, ty}; use clippy_utils::{
diagnostics::{self, span_lint_and_sugg},
meets_msrv, msrvs, source, ty,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::*; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym; use rustc_span::{source_map::Spanned, sym};
declare_clippy_lint! {
/// ### What it does
/// Lints subtraction between `Instant::now()` and another `Instant`.
///
/// ### Why is this bad?
/// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
/// as `Instant` subtraction saturates.
///
/// `prev_instant.elapsed()` also more clearly signals intention.
///
/// ### Example
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = Instant::now() - prev_instant;
/// ```
/// Use instead:
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = prev_instant.elapsed();
/// ```
#[clippy::version = "1.64.0"]
pub MANUAL_INSTANT_ELAPSED,
pedantic,
"subtraction between `Instant::now()` and previous `Instant`"
}
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`]. /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`].
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Unchecked subtraction could cause underflow on certain platforms, leading to bugs and/or /// Unchecked subtraction could cause underflow on certain platforms, leading to
/// unintentional panics. /// unintentional panics.
/// ///
/// ### Example /// ### Example
@ -32,21 +63,39 @@
"finds unchecked subtraction of a 'Duration' from an 'Instant'" "finds unchecked subtraction of a 'Duration' from an 'Instant'"
} }
pub struct UncheckedDurationSubtraction { pub struct InstantSubtraction {
msrv: Option<RustcVersion>, msrv: Option<RustcVersion>,
} }
impl UncheckedDurationSubtraction { impl InstantSubtraction {
#[must_use] #[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self { pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv } Self { msrv }
} }
} }
impl_lint_pass!(UncheckedDurationSubtraction => [UNCHECKED_DURATION_SUBTRACTION]); impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]);
impl LateLintPass<'_> for InstantSubtraction {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Binary(Spanned {node: BinOpKind::Sub, ..}, lhs, rhs) = expr.kind
&& check_instant_now_call(cx, lhs)
&& let ty_resolved = cx.typeck_results().expr_ty(rhs)
&& let rustc_middle::ty::Adt(def, _) = ty_resolved.kind()
&& clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT)
&& let Some(sugg) = clippy_utils::sugg::Sugg::hir_opt(cx, rhs)
{
span_lint_and_sugg(
cx,
MANUAL_INSTANT_ELAPSED,
expr.span,
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_par()),
Applicability::MachineApplicable,
);
}
impl<'tcx> LateLintPass<'tcx> for UncheckedDurationSubtraction {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() || !meets_msrv(self.msrv, msrvs::TRY_FROM) { if expr.span.from_expansion() || !meets_msrv(self.msrv, msrvs::TRY_FROM) {
return; return;
} }
@ -67,6 +116,17 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
} }
} }
fn check_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
if let ExprKind::Call(fn_expr, []) = expr_block.kind
&& let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
&& clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
{
true
} else {
false
}
}
fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr); let expr_ty = cx.typeck_results().expr_ty(expr);

View File

@ -151,6 +151,7 @@
mod inherent_to_string; mod inherent_to_string;
mod init_numbered_fields; mod init_numbered_fields;
mod inline_fn_without_body; mod inline_fn_without_body;
mod instant_subtraction;
mod int_plus_one; mod int_plus_one;
mod invalid_upcast_comparisons; mod invalid_upcast_comparisons;
mod invalid_utf8_in_unchecked; mod invalid_utf8_in_unchecked;
@ -172,7 +173,6 @@
mod manual_async_fn; mod manual_async_fn;
mod manual_bits; mod manual_bits;
mod manual_clamp; mod manual_clamp;
mod manual_instant_elapsed;
mod manual_is_ascii_check; mod manual_is_ascii_check;
mod manual_let_else; mod manual_let_else;
mod manual_non_exhaustive; mod manual_non_exhaustive;
@ -279,7 +279,6 @@
mod trait_bounds; mod trait_bounds;
mod transmute; mod transmute;
mod types; mod types;
mod unchecked_duration_subtraction;
mod undocumented_unsafe_blocks; mod undocumented_unsafe_blocks;
mod unicode; mod unicode;
mod uninit_vec; mod uninit_vec;
@ -908,7 +907,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold))); store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
store.register_late_pass(|_| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked)); store.register_late_pass(|_| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default()); store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
store.register_late_pass(|_| Box::new(manual_instant_elapsed::ManualInstantElapsed)); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv)));
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(msrv))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(msrv)));
store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew));

View File

@ -1,69 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Lints subtraction between `Instant::now()` and another `Instant`.
///
/// ### Why is this bad?
/// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
/// as `Instant` subtraction saturates.
///
/// `prev_instant.elapsed()` also more clearly signals intention.
///
/// ### Example
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = Instant::now() - prev_instant;
/// ```
/// Use instead:
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = prev_instant.elapsed();
/// ```
#[clippy::version = "1.65.0"]
pub MANUAL_INSTANT_ELAPSED,
pedantic,
"subtraction between `Instant::now()` and previous `Instant`"
}
declare_lint_pass!(ManualInstantElapsed => [MANUAL_INSTANT_ELAPSED]);
impl LateLintPass<'_> for ManualInstantElapsed {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Binary(Spanned {node: BinOpKind::Sub, ..}, lhs, rhs) = expr.kind
&& check_instant_now_call(cx, lhs)
&& let ty_resolved = cx.typeck_results().expr_ty(rhs)
&& let rustc_middle::ty::Adt(def, _) = ty_resolved.kind()
&& clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT)
&& let Some(sugg) = clippy_utils::sugg::Sugg::hir_opt(cx, rhs)
{
span_lint_and_sugg(
cx,
MANUAL_INSTANT_ELAPSED,
expr.span,
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_par()),
Applicability::MachineApplicable,
);
}
}
}
fn check_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
if let ExprKind::Call(fn_expr, []) = expr_block.kind
&& let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
&& clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
{
true
} else {
false
}
}