From: Nadir Fejzic Date: Mon, 7 Nov 2022 19:58:49 +0000 (+0100) Subject: refactor: lint man. instant elapsed and unch. dur. subtr. in single pass X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=2f2eb2e4baace68868af2f4ac3ec7aed073139ad;p=rust.git refactor: lint man. instant elapsed and unch. dur. subtr. in single pass --- diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs new file mode 100644 index 00000000000..8cc5643c9a7 --- /dev/null +++ b/clippy_lints/src/instant_subtraction.rs @@ -0,0 +1,165 @@ +use clippy_utils::{ + diagnostics::{self, span_lint_and_sugg}, + meets_msrv, msrvs, source, ty, +}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +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! { + /// ### What it does + /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`]. + /// + /// ### Why is this bad? + /// Unchecked subtraction could cause underflow on certain platforms, leading to + /// unintentional panics. + /// + /// ### Example + /// ```rust + /// let time_passed = Instant::now() - Duration::from_secs(5); + /// ``` + /// + /// Use instead: + /// ```rust + /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); + /// ``` + /// + /// [`Duration`]: std::time::Duration + /// [`Instant::now()`]: std::time::Instant::now; + #[clippy::version = "1.65.0"] + pub UNCHECKED_DURATION_SUBTRACTION, + suspicious, + "finds unchecked subtraction of a 'Duration' from an 'Instant'" +} + +pub struct InstantSubtraction { + msrv: Option, +} + +impl InstantSubtraction { + #[must_use] + pub fn new(msrv: Option) -> 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 + && 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, + ); + } + + if expr.span.from_expansion() || !meets_msrv(self.msrv, msrvs::TRY_FROM) { + return; + } + + if_chain! { + if let ExprKind::Binary(op, lhs, rhs) = expr.kind; + + if let BinOpKind::Sub = op.node; + + // get types of left and right side + if is_an_instant(cx, lhs); + if is_a_duration(cx, rhs); + + then { + print_lint_and_sugg(cx, lhs, rhs, expr) + } + } + } +} + +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 { + 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_lint_and_sugg(cx: &LateContext<'_>, left_expr: &Expr<'_>, right_expr: &Expr<'_>, expr: &Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + + let left_expr = + source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability); + let right_expr = source::snippet_with_applicability( + cx, + right_expr.span, + "std::time::Duration::from_secs(1)", + &mut applicability, + ); + + diagnostics::span_lint_and_sugg( + cx, + UNCHECKED_DURATION_SUBTRACTION, + expr.span, + "unchecked subtraction of a 'Duration' from an 'Instant'", + "try", + format!("{}.checked_sub({}).unwrap();", left_expr, right_expr), + applicability, + ); +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a5407b65b88..ce0e7ebfbf1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -151,6 +151,7 @@ mod inherent_to_string; mod init_numbered_fields; mod inline_fn_without_body; +mod instant_subtraction; mod int_plus_one; mod invalid_upcast_comparisons; mod invalid_utf8_in_unchecked; @@ -172,7 +173,6 @@ mod manual_async_fn; mod manual_bits; mod manual_clamp; -mod manual_instant_elapsed; mod manual_is_ascii_check; mod manual_let_else; mod manual_non_exhaustive; @@ -279,7 +279,6 @@ mod trait_bounds; mod transmute; mod types; -mod unchecked_duration_subtraction; mod undocumented_unsafe_blocks; mod unicode; 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(|_| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked)); store.register_late_pass(|_| Box::::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(move |_| Box::new(manual_clamp::ManualClamp::new(msrv))); store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); diff --git a/clippy_lints/src/manual_instant_elapsed.rs b/clippy_lints/src/manual_instant_elapsed.rs deleted file mode 100644 index 1e60aa02d3c..00000000000 --- a/clippy_lints/src/manual_instant_elapsed.rs +++ /dev/null @@ -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 - } -} diff --git a/clippy_lints/src/unchecked_duration_subtraction.rs b/clippy_lints/src/unchecked_duration_subtraction.rs deleted file mode 100644 index ae796000662..00000000000 --- a/clippy_lints/src/unchecked_duration_subtraction.rs +++ /dev/null @@ -1,105 +0,0 @@ -use clippy_utils::{diagnostics, meets_msrv, msrvs, source, ty}; -use rustc_errors::Applicability; -use rustc_hir::*; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`]. - /// - /// ### Why is this bad? - /// Unchecked subtraction could cause underflow on certain platforms, leading to bugs and/or - /// unintentional panics. - /// - /// ### Example - /// ```rust - /// let time_passed = Instant::now() - Duration::from_secs(5); - /// ``` - /// - /// Use instead: - /// ```rust - /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); - /// ``` - /// - /// [`Duration`]: std::time::Duration - /// [`Instant::now()`]: std::time::Instant::now; - #[clippy::version = "1.65.0"] - pub UNCHECKED_DURATION_SUBTRACTION, - suspicious, - "finds unchecked subtraction of a 'Duration' from an 'Instant'" -} - -pub struct UncheckedDurationSubtraction { - msrv: Option, -} - -impl UncheckedDurationSubtraction { - #[must_use] - pub fn new(msrv: Option) -> Self { - Self { msrv } - } -} - -impl_lint_pass!(UncheckedDurationSubtraction => [UNCHECKED_DURATION_SUBTRACTION]); - -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) { - return; - } - - if_chain! { - if let ExprKind::Binary(op, lhs, rhs) = expr.kind; - - if let BinOpKind::Sub = op.node; - - // get types of left and right side - if is_an_instant(cx, lhs); - if is_a_duration(cx, rhs); - - then { - print_lint_and_sugg(cx, lhs, rhs, expr) - } - } - } -} - -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_lint_and_sugg(cx: &LateContext<'_>, left_expr: &Expr<'_>, right_expr: &Expr<'_>, expr: &Expr<'_>) { - let mut applicability = Applicability::MachineApplicable; - - let left_expr = - source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability); - let right_expr = source::snippet_with_applicability( - cx, - right_expr.span, - "std::time::Duration::from_secs(1)", - &mut applicability, - ); - - diagnostics::span_lint_and_sugg( - cx, - UNCHECKED_DURATION_SUBTRACTION, - expr.span, - "unchecked subtraction of a 'Duration' from an 'Instant'", - "try", - format!("{}.checked_sub({}).unwrap();", left_expr, right_expr), - applicability, - ); -}