| use clippy_utils::diagnostics::{self, span_lint_and_sugg}; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::source::snippet_with_context; |
| use clippy_utils::sugg::Sugg; |
| use clippy_utils::ty; |
| use rustc_errors::Applicability; |
| use rustc_hir::{BinOpKind, Expr, ExprKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| use rustc_span::source_map::Spanned; |
| use rustc_span::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.65.0"] |
| pub MANUAL_INSTANT_ELAPSED, |
| pedantic, |
| "subtraction between `Instant::now()` and previous `Instant`" |
| } |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Lints subtraction between an [`Instant`] and a [`Duration`]. |
| /// |
| /// ### Why is this bad? |
| /// Unchecked subtraction could cause underflow on certain platforms, leading to |
| /// unintentional panics. |
| /// |
| /// ### Example |
| /// ```rust |
| /// # use std::time::{Instant, Duration}; |
| /// let time_passed = Instant::now() - Duration::from_secs(5); |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```rust |
| /// # use std::time::{Instant, Duration}; |
| /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); |
| /// ``` |
| /// |
| /// [`Duration`]: std::time::Duration |
| /// [`Instant::now()`]: std::time::Instant::now; |
| #[clippy::version = "1.67.0"] |
| pub UNCHECKED_DURATION_SUBTRACTION, |
| pedantic, |
| "finds unchecked subtraction of a 'Duration' from an 'Instant'" |
| } |
| |
| pub struct InstantSubtraction { |
| msrv: Msrv, |
| } |
| |
| impl InstantSubtraction { |
| #[must_use] |
| pub fn new(msrv: Msrv) -> Self { |
| Self { msrv } |
| } |
| } |
| |
| 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 |
| { |
| if_chain! { |
| if is_instant_now_call(cx, lhs); |
| |
| if is_an_instant(cx, rhs); |
| if let Some(sugg) = Sugg::hir_opt(cx, rhs); |
| |
| then { |
| print_manual_instant_elapsed_sugg(cx, expr, sugg) |
| } else { |
| if_chain! { |
| if !expr.span.from_expansion(); |
| if self.msrv.meets(msrvs::TRY_FROM); |
| |
| if is_an_instant(cx, lhs); |
| if is_a_duration(cx, rhs); |
| |
| then { |
| print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| extract_msrv_attr!(LateContext); |
| } |
| |
| fn is_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 { |
| let expr_ty = cx.typeck_results().expr_ty(expr); |
| |
| match expr_ty.kind() { |
| rustc_middle::ty::Adt(def, _) => clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT), |
| _ => false, |
| } |
| } |
| |
| fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| let expr_ty = cx.typeck_results().expr_ty(expr); |
| ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration) |
| } |
| |
| fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { |
| span_lint_and_sugg( |
| cx, |
| MANUAL_INSTANT_ELAPSED, |
| expr.span, |
| "manual implementation of `Instant::elapsed`", |
| "try", |
| format!("{}.elapsed()", sugg.maybe_par()), |
| Applicability::MachineApplicable, |
| ); |
| } |
| |
| fn print_unchecked_duration_subtraction_sugg( |
| cx: &LateContext<'_>, |
| left_expr: &Expr<'_>, |
| right_expr: &Expr<'_>, |
| expr: &Expr<'_>, |
| ) { |
| let mut applicability = Applicability::MachineApplicable; |
| |
| let ctxt = expr.span.ctxt(); |
| let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "<instant>", &mut applicability).0; |
| let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "<duration>", &mut applicability).0; |
| |
| diagnostics::span_lint_and_sugg( |
| cx, |
| UNCHECKED_DURATION_SUBTRACTION, |
| expr.span, |
| "unchecked subtraction of a 'Duration' from an 'Instant'", |
| "try", |
| format!("{left_expr}.checked_sub({right_expr}).unwrap()"), |
| applicability, |
| ); |
| } |