Skip to content

Commit 041fd68

Browse files
committed
feat: add and implement unchecked_duration_subtraction lint
1 parent a80e278 commit 041fd68

File tree

7 files changed

+114
-1
lines changed

7 files changed

+114
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,7 @@ Released 2018-09-13
41164116
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
41174117
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
41184118
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
4119+
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
41194120
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
41204121
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
41214122
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
332332
LintId::of(types::REDUNDANT_ALLOCATION),
333333
LintId::of(types::TYPE_COMPLEXITY),
334334
LintId::of(types::VEC_BOX),
335+
LintId::of(unchecked_duration_subtraction::UNCHECKED_DURATION_SUBTRACTION),
335336
LintId::of(unicode::INVISIBLE_CHARACTERS),
336337
LintId::of(uninit_vec::UNINIT_VEC),
337338
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ store.register_lints(&[
562562
types::REDUNDANT_ALLOCATION,
563563
types::TYPE_COMPLEXITY,
564564
types::VEC_BOX,
565+
unchecked_duration_subtraction::UNCHECKED_DURATION_SUBTRACTION,
565566
undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
566567
unicode::INVISIBLE_CHARACTERS,
567568
unicode::NON_ASCII_LITERAL,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
3535
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
3636
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
3737
LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF),
38+
LintId::of(unchecked_duration_subtraction::UNCHECKED_DURATION_SUBTRACTION),
3839
LintId::of(unused_peekable::UNUSED_PEEKABLE),
3940
LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS),
4041
])

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ mod trailing_empty_array;
369369
mod trait_bounds;
370370
mod transmute;
371371
mod types;
372+
mod unchecked_duration_subtraction;
372373
mod undocumented_unsafe_blocks;
373374
mod unicode;
374375
mod uninit_vec;
@@ -900,6 +901,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
900901
store.register_late_pass(|| Box::new(manual_string_new::ManualStringNew));
901902
store.register_late_pass(|| Box::new(unused_peekable::UnusedPeekable));
902903
store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments));
904+
store.register_late_pass(move || Box::new(unchecked_duration_subtraction::UncheckedDurationSubtraction::new(msrv)));
903905
// add lints here, do not remove this comment, it's used in `new_lint`
904906
}
905907

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.
216+
/// Lint: UNCHECKED_DURATION_SUBTRACTION, 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.
217217
///
218218
/// The minimum rust version that the project supports
219219
(msrv: Option<String> = None),

0 commit comments

Comments
 (0)