Skip to content

Commit 097d049

Browse files
committed
Add Arithmetic lint
1 parent f93d418 commit 097d049

13 files changed

+274
-52
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3436,6 +3436,7 @@ Released 2018-09-13
34363436
[`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range
34373437
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
34383438
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
3439+
[`arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic
34393440
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
34403441
[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
34413442
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants

clippy_lints/src/arithmetic.rs

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
2+
use rustc_data_structures::fx::FxHashSet;
3+
use rustc_hir as hir;
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::{declare_tool_lint, impl_lint_pass};
6+
use rustc_span::source_map::Span;
7+
8+
const HARD_CODED_ALLOWED: &[&str] = &["Saturating", "String", "Wrapping"];
9+
10+
declare_clippy_lint! {
11+
#[clippy::version = "1.63.0"]
12+
pub ARITHMETIC,
13+
restriction,
14+
"any arithmetic expression that could overflow or panic"
15+
}
16+
17+
#[derive(Debug)]
18+
pub struct Arithmetic {
19+
allowed: FxHashSet<String>,
20+
/// Used to check whether expressions are constants, such as in enum discriminants and consts
21+
const_span: Option<Span>,
22+
expr_span: Option<Span>,
23+
}
24+
25+
impl_lint_pass!(Arithmetic => [ARITHMETIC]);
26+
27+
impl Arithmetic {
28+
#[must_use]
29+
pub fn new(mut allowed: FxHashSet<String>) -> Self {
30+
allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
31+
Self {
32+
allowed,
33+
const_span: None,
34+
expr_span: None,
35+
}
36+
}
37+
38+
/// Checks if the given `expr` has any of the inner `allowed` elements.
39+
fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
40+
match expr.kind {
41+
hir::ExprKind::Call(ref local_expr, _) => self.is_allowed_ty(cx, local_expr),
42+
hir::ExprKind::Path(ref qpath) => {
43+
let path = if let hir::QPath::Resolved(_, ref path) = qpath {
44+
if let hir::def::Res::Local(hid) = path.res
45+
&& let parent_node_id = cx.tcx.hir().get_parent_node(hid)
46+
&& let Some(hir::Node::Local(local)) = cx.tcx.hir().find(parent_node_id)
47+
&& let Some(ty) = local.ty
48+
&& let hir::TyKind::Path(hir::QPath::Resolved(_, ref path)) = ty.kind
49+
{
50+
path
51+
}
52+
else {
53+
path
54+
}
55+
} else if let hir::QPath::TypeRelative(ref ty, _) = qpath
56+
&& let hir::TyKind::Path(hir::QPath::Resolved(_, ref path)) = ty.kind
57+
{
58+
path
59+
}
60+
else {
61+
return false;
62+
};
63+
for segment in path.segments {
64+
if self.allowed.contains(segment.ident.as_str()) {
65+
return true;
66+
}
67+
}
68+
false
69+
},
70+
_ => false,
71+
}
72+
}
73+
74+
fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
75+
span_lint(cx, ARITHMETIC, expr.span, "arithmetic detected");
76+
self.expr_span = Some(expr.span);
77+
}
78+
}
79+
80+
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
81+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
82+
if self.expr_span.is_some() {
83+
return;
84+
}
85+
if let Some(span) = self.const_span && span.contains(expr.span) {
86+
return;
87+
}
88+
match &expr.kind {
89+
hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
90+
let (
91+
hir::BinOpKind::Add
92+
| hir::BinOpKind::Sub
93+
| hir::BinOpKind::Mul
94+
| hir::BinOpKind::Div
95+
| hir::BinOpKind::Rem
96+
| hir::BinOpKind::Shl
97+
| hir::BinOpKind::Shr
98+
) = op.node else {
99+
return;
100+
};
101+
if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
102+
return;
103+
}
104+
self.issue_lint(cx, expr);
105+
},
106+
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
107+
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
108+
self.issue_lint(cx, expr);
109+
}
110+
},
111+
_ => {},
112+
}
113+
}
114+
115+
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
116+
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
117+
match cx.tcx.hir().body_owner_kind(body_owner) {
118+
hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => {
119+
let body_span = cx.tcx.def_span(body_owner);
120+
if let Some(span) = self.const_span && span.contains(body_span) {
121+
return;
122+
}
123+
self.const_span = Some(body_span);
124+
},
125+
hir::BodyOwnerKind::Closure | hir::BodyOwnerKind::Fn => {},
126+
}
127+
}
128+
129+
fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
130+
let body_owner = cx.tcx.hir().body_owner(body.id());
131+
let body_span = cx.tcx.hir().span(body_owner);
132+
if let Some(span) = self.const_span && span.contains(body_span) {
133+
return;
134+
}
135+
self.const_span = None;
136+
}
137+
138+
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
139+
if Some(expr.span) == self.expr_span {
140+
self.expr_span = None;
141+
}
142+
}
143+
}

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ store.register_lints(&[
3737
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
3838
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
3939
approx_const::APPROX_CONSTANT,
40+
arithmetic::ARITHMETIC,
4041
as_conversions::AS_CONVERSIONS,
4142
as_underscore::AS_UNDERSCORE,
4243
asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,

clippy_lints/src/lib.register_restriction.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Manual edits will be overwritten.
44

55
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
6+
LintId::of(arithmetic::ARITHMETIC),
67
LintId::of(as_conversions::AS_CONVERSIONS),
78
LintId::of(as_underscore::AS_UNDERSCORE),
89
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ mod renamed_lints;
170170
// begin lints modules, do not remove this comment, it’s used in `update_lints`
171171
mod almost_complete_letter_range;
172172
mod approx_const;
173+
mod arithmetic;
173174
mod as_conversions;
174175
mod as_underscore;
175176
mod asm_syntax;
@@ -547,6 +548,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
547548
store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
548549
}
549550

551+
let arithmetic_allowed = conf.arithmetic_allowed.clone();
552+
store.register_late_pass(move || Box::new(arithmetic::Arithmetic::new(arithmetic_allowed.clone())));
550553
store.register_late_pass(|| Box::new(utils::dump_hir::DumpHir));
551554
store.register_late_pass(|| Box::new(utils::author::Author));
552555
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();

clippy_lints/src/utils/conf.rs

+2
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ macro_rules! define_Conf {
191191
}
192192

193193
define_Conf! {
194+
/// PLACEHOLDER
195+
(arithmetic_allowed: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
194196
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
195197
///
196198
/// Suppress lints whenever the suggested change would cause breakage for other crates.

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
33
allow-expect-in-tests
44
allow-unwrap-in-tests
55
allowed-scripts
6+
arithmetic-allowed
67
array-size-threshold
78
avoid-breaking-exported-api
89
await-holding-invalid-types

tests/ui/arithmetic.fixed

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![allow(clippy::unnecessary_owned_empty_strings)]
4+
#![feature(saturating_int_impl)]
5+
#![warn(clippy::arithmetic)]
6+
7+
use core::num::{Saturating, Wrapping};
8+
9+
pub fn hard_coded_allowed() {
10+
let _ = Saturating(0u32) + Saturating(0u32);
11+
let _ = Wrapping(0u32) + Wrapping(0u32);
12+
let _ = String::new() + "";
13+
14+
let saturating: Saturating<u32> = Saturating(0u32);
15+
let wrapping: Wrapping<u32> = Wrapping(0u32);
16+
let string: String = String::new();
17+
18+
let _ = saturating + saturating;
19+
let _ = wrapping + wrapping;
20+
let _ = string + "";
21+
}
22+
23+
fn main() {}

tests/ui/arithmetic.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![allow(clippy::unnecessary_owned_empty_strings)]
4+
#![feature(saturating_int_impl)]
5+
#![warn(clippy::arithmetic)]
6+
7+
use core::num::{Saturating, Wrapping};
8+
9+
pub fn hard_coded_allowed() {
10+
let _ = Saturating(0u32) + Saturating(0u32);
11+
let _ = Wrapping(0u32) + Wrapping(0u32);
12+
let _ = String::new() + "";
13+
14+
let saturating: Saturating<u32> = Saturating(0u32);
15+
let wrapping: Wrapping<u32> = Wrapping(0u32);
16+
let string: String = String::new();
17+
18+
let _ = saturating + saturating;
19+
let _ = wrapping + wrapping;
20+
let _ = string + "";
21+
}
22+
23+
fn main() {}

tests/ui/float_arithmetic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)]
1+
#![warn(clippy::arithmetic)]
22
#![allow(
33
unused,
44
clippy::shadow_reuse,

tests/ui/float_arithmetic.stderr

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,102 @@
1-
error: floating-point arithmetic detected
1+
error: arithmetic detected
22
--> $DIR/float_arithmetic.rs:15:5
33
|
44
LL | f * 2.0;
55
| ^^^^^^^
66
|
7-
= note: `-D clippy::float-arithmetic` implied by `-D warnings`
7+
= note: `-D clippy::arithmetic` implied by `-D warnings`
88

9-
error: floating-point arithmetic detected
9+
error: arithmetic detected
1010
--> $DIR/float_arithmetic.rs:17:5
1111
|
1212
LL | 1.0 + f;
1313
| ^^^^^^^
1414

15-
error: floating-point arithmetic detected
15+
error: arithmetic detected
1616
--> $DIR/float_arithmetic.rs:18:5
1717
|
1818
LL | f * 2.0;
1919
| ^^^^^^^
2020

21-
error: floating-point arithmetic detected
21+
error: arithmetic detected
2222
--> $DIR/float_arithmetic.rs:19:5
2323
|
2424
LL | f / 2.0;
2525
| ^^^^^^^
2626

27-
error: floating-point arithmetic detected
27+
error: arithmetic detected
2828
--> $DIR/float_arithmetic.rs:20:5
2929
|
3030
LL | f - 2.0 * 4.2;
3131
| ^^^^^^^^^^^^^
3232

33-
error: floating-point arithmetic detected
33+
error: arithmetic detected
3434
--> $DIR/float_arithmetic.rs:21:5
3535
|
3636
LL | -f;
3737
| ^^
3838

39-
error: floating-point arithmetic detected
39+
error: arithmetic detected
4040
--> $DIR/float_arithmetic.rs:23:5
4141
|
4242
LL | f += 1.0;
4343
| ^^^^^^^^
4444

45-
error: floating-point arithmetic detected
45+
error: arithmetic detected
4646
--> $DIR/float_arithmetic.rs:24:5
4747
|
4848
LL | f -= 1.0;
4949
| ^^^^^^^^
5050

51-
error: floating-point arithmetic detected
51+
error: arithmetic detected
5252
--> $DIR/float_arithmetic.rs:25:5
5353
|
5454
LL | f *= 2.0;
5555
| ^^^^^^^^
5656

57-
error: floating-point arithmetic detected
57+
error: arithmetic detected
5858
--> $DIR/float_arithmetic.rs:26:5
5959
|
6060
LL | f /= 2.0;
6161
| ^^^^^^^^
6262

63-
error: floating-point arithmetic detected
63+
error: arithmetic detected
6464
--> $DIR/float_arithmetic.rs:32:5
6565
|
6666
LL | 3.1_f32 + &1.2_f32;
6767
| ^^^^^^^^^^^^^^^^^^
6868

69-
error: floating-point arithmetic detected
69+
error: arithmetic detected
7070
--> $DIR/float_arithmetic.rs:33:5
7171
|
7272
LL | &3.4_f32 + 1.5_f32;
7373
| ^^^^^^^^^^^^^^^^^^
7474

75-
error: floating-point arithmetic detected
75+
error: arithmetic detected
7676
--> $DIR/float_arithmetic.rs:34:5
7777
|
7878
LL | &3.5_f32 + &1.3_f32;
7979
| ^^^^^^^^^^^^^^^^^^^
8080

81-
error: floating-point arithmetic detected
81+
error: arithmetic detected
8282
--> $DIR/float_arithmetic.rs:39:5
8383
|
8484
LL | a + f
8585
| ^^^^^
8686

87-
error: floating-point arithmetic detected
87+
error: arithmetic detected
8888
--> $DIR/float_arithmetic.rs:43:5
8989
|
9090
LL | f1 + f2
9191
| ^^^^^^^
9292

93-
error: floating-point arithmetic detected
93+
error: arithmetic detected
9494
--> $DIR/float_arithmetic.rs:47:5
9595
|
9696
LL | f1 + f2
9797
| ^^^^^^^
9898

99-
error: floating-point arithmetic detected
99+
error: arithmetic detected
100100
--> $DIR/float_arithmetic.rs:51:5
101101
|
102102
LL | (&f1 + &f2)

tests/ui/integer_arithmetic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)]
1+
#![warn(clippy::arithmetic)]
22
#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::op_ref)]
33

44
#[rustfmt::skip]

0 commit comments

Comments
 (0)