]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/unchecked_duration_subtraction.rs
fb08067de8d4fd8be64823dea0044c4e671f8758
[rust.git] / clippy_lints / src / unchecked_duration_subtraction.rs
1 use clippy_utils::{diagnostics, meets_msrv, msrvs, source, ty};
2 use rustc_errors::Applicability;
3 use rustc_hir::*;
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_semver::RustcVersion;
6 use rustc_session::{declare_tool_lint, impl_lint_pass};
7 use rustc_span::symbol::sym;
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`].
12     ///
13     /// ### Why is this bad?
14     /// Unchecked subtraction could cause underflow on certain platforms, leading to bugs and/or
15     /// unintentional panics.
16     ///
17     /// ### Example
18     /// ```rust
19     /// let time_passed = Instant::now() - Duration::from_secs(5);
20     /// ```
21     ///
22     /// Use instead:
23     /// ```rust
24     /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
25     /// ```
26     ///
27     /// [`Duration`]: std::time::Duration
28     /// [`Instant::now()`]: std::time::Instant::now;
29     #[clippy::version = "1.65.0"]
30     pub UNCHECKED_DURATION_SUBTRACTION,
31     suspicious,
32     "finds unchecked subtraction of a 'Duration' from an 'Instant'"
33 }
34
35 pub struct UncheckedDurationSubtraction {
36     msrv: Option<RustcVersion>,
37 }
38
39 impl UncheckedDurationSubtraction {
40     #[must_use]
41     pub fn new(msrv: Option<RustcVersion>) -> Self {
42         Self { msrv }
43     }
44 }
45
46 impl_lint_pass!(UncheckedDurationSubtraction => [UNCHECKED_DURATION_SUBTRACTION]);
47
48 impl<'tcx> LateLintPass<'tcx> for UncheckedDurationSubtraction {
49     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
50         if expr.span.from_expansion() || !meets_msrv(self.msrv, msrvs::TRY_FROM) {
51             return;
52         }
53
54         if_chain! {
55             if let ExprKind::Binary(op, lhs, rhs) = expr.kind;
56
57             if let BinOpKind::Sub = op.node;
58
59             // get types of left and right side
60             if is_an_instant(cx, lhs);
61             if is_a_duration(cx, rhs);
62
63             then {
64                 print_lint_and_sugg(cx, lhs, rhs, expr)
65             }
66         }
67     }
68 }
69
70 fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
71     let expr_ty = cx.typeck_results().expr_ty(expr);
72
73     match expr_ty.kind() {
74         rustc_middle::ty::Adt(def, _) => {
75             clippy_utils::match_def_path(cx, dbg!(def).did(), &clippy_utils::paths::INSTANT)
76         },
77         _ => false,
78     }
79 }
80
81 fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
82     let expr_ty = cx.typeck_results().expr_ty(expr);
83     ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration)
84 }
85
86 fn print_lint_and_sugg(cx: &LateContext<'_>, left_expr: &Expr<'_>, right_expr: &Expr<'_>, expr: &Expr<'_>) {
87     let mut applicability = Applicability::MachineApplicable;
88
89     let left_expr =
90         source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability);
91     let right_expr = source::snippet_with_applicability(
92         cx,
93         right_expr.span,
94         "std::time::Duration::from_secs(1)",
95         &mut applicability,
96     );
97
98     diagnostics::span_lint_and_sugg(
99         cx,
100         UNCHECKED_DURATION_SUBTRACTION,
101         expr.span,
102         "unchecked subtraction of a 'Duration' from an 'Instant'",
103         "try",
104         format!("{}.checked_sub({}).unwrap();", left_expr, right_expr),
105         applicability,
106     );
107 }