Skip to content

Commit 708c2d9

Browse files
committed
feat: add and implement unchecked_duration_subtraction lint
1 parent 432baf7 commit 708c2d9

File tree

4 files changed

+111
-1
lines changed

4 files changed

+111
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4428,6 +4428,7 @@ Released 2018-09-13
44284428
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
44294429
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
44304430
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
4431+
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
44314432
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
44324433
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
44334434
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ mod trailing_empty_array;
279279
mod trait_bounds;
280280
mod transmute;
281281
mod types;
282+
mod unchecked_duration_subtraction;
282283
mod undocumented_unsafe_blocks;
283284
mod unicode;
284285
mod uninit_vec;
@@ -921,6 +922,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
921922
store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr));
922923
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
923924
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv)));
925+
store.register_late_pass(move || Box::new(unchecked_duration_subtraction::UncheckedDurationSubtraction::new(msrv)));
924926
// add lints here, do not remove this comment, it's used in `new_lint`
925927
}
926928

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
}

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ define_Conf! {
213213
///
214214
/// Suppress lints whenever the suggested change would cause breakage for other crates.
215215
(avoid_breaking_exported_api: bool = true),
216-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE.
216+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION.
217217
///
218218
/// The minimum rust version that the project supports
219219
(msrv: Option<String> = None),

0 commit comments

Comments
 (0)