Skip to content

Commit 617417e

Browse files
committed
Auto merge of #9365 - c410-f3r:arith, r=Alexendoo
[Arithmetic] Consider literals Fixes rust-lang/rust-clippy#9307 and makes the `arithmetic` lint behave like `integer_arithmetic`. It is worth noting that literal integers of a binary operation (`1 + 1`, `i32::MAX + 1`), **regardless if they are in a constant environment**, won't trigger the lint. Assign operations also have similar reasoning. changelog: Consider literals in the arithmetic lint
2 parents da29f89 + 0d078c9 commit 617417e

File tree

6 files changed

+168
-80
lines changed

6 files changed

+168
-80
lines changed

clippy_lints/src/operators/arithmetic.rs

+86-32
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55

66
use super::ARITHMETIC;
77
use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
8+
use rustc_ast as ast;
89
use rustc_data_structures::fx::FxHashSet;
910
use rustc_hir as hir;
1011
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::ty::Ty;
1113
use rustc_session::impl_lint_pass;
12-
use rustc_span::source_map::Span;
14+
use rustc_span::source_map::{Span, Spanned};
1315

14-
const HARD_CODED_ALLOWED: &[&str] = &["std::num::Saturating", "std::string::String", "std::num::Wrapping"];
16+
const HARD_CODED_ALLOWED: &[&str] = &[
17+
"f32",
18+
"f64",
19+
"std::num::Saturating",
20+
"std::string::String",
21+
"std::num::Wrapping",
22+
];
1523

1624
#[derive(Debug)]
1725
pub struct Arithmetic {
@@ -34,6 +42,27 @@ impl Arithmetic {
3442
}
3543
}
3644

45+
/// Checks assign operators (+=, -=, *=, /=) of integers in a non-constant environment that
46+
/// won't overflow.
47+
fn has_valid_assign_op(op: &Spanned<hir::BinOpKind>, rhs: &hir::Expr<'_>, rhs_refs: Ty<'_>) -> bool {
48+
if !Self::is_literal_integer(rhs, rhs_refs) {
49+
return false;
50+
}
51+
if let hir::BinOpKind::Div | hir::BinOpKind::Mul = op.node
52+
&& let hir::ExprKind::Lit(ref lit) = rhs.kind
53+
&& let ast::LitKind::Int(1, _) = lit.node
54+
{
55+
return true;
56+
}
57+
false
58+
}
59+
60+
/// Checks "raw" binary operators (+, -, *, /) of integers in a non-constant environment
61+
/// already handled by the CTFE.
62+
fn has_valid_bin_op(lhs: &hir::Expr<'_>, lhs_refs: Ty<'_>, rhs: &hir::Expr<'_>, rhs_refs: Ty<'_>) -> bool {
63+
Self::is_literal_integer(lhs, lhs_refs) && Self::is_literal_integer(rhs, rhs_refs)
64+
}
65+
3766
/// Checks if the given `expr` has any of the inner `allowed` elements.
3867
fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
3968
self.allowed.contains(
@@ -46,40 +75,66 @@ impl Arithmetic {
4675
)
4776
}
4877

78+
/// Explicit integers like `1` or `i32::MAX`. Does not take into consideration references.
79+
fn is_literal_integer(expr: &hir::Expr<'_>, expr_refs: Ty<'_>) -> bool {
80+
let is_integral = expr_refs.is_integral();
81+
let is_literal = matches!(expr.kind, hir::ExprKind::Lit(_));
82+
is_integral && is_literal
83+
}
84+
4985
fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
5086
span_lint(cx, ARITHMETIC, expr.span, "arithmetic detected");
5187
self.expr_span = Some(expr.span);
5288
}
89+
90+
/// Manages when the lint should be triggered. Operations in constant environments, hard coded
91+
/// types, custom allowed types and non-constant operations that won't overflow are ignored.
92+
fn manage_bin_ops(
93+
&mut self,
94+
cx: &LateContext<'_>,
95+
expr: &hir::Expr<'_>,
96+
op: &Spanned<hir::BinOpKind>,
97+
lhs: &hir::Expr<'_>,
98+
rhs: &hir::Expr<'_>,
99+
) {
100+
if constant_simple(cx, cx.typeck_results(), expr).is_some() {
101+
return;
102+
}
103+
if !matches!(
104+
op.node,
105+
hir::BinOpKind::Add
106+
| hir::BinOpKind::Sub
107+
| hir::BinOpKind::Mul
108+
| hir::BinOpKind::Div
109+
| hir::BinOpKind::Rem
110+
| hir::BinOpKind::Shl
111+
| hir::BinOpKind::Shr
112+
) {
113+
return;
114+
};
115+
if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
116+
return;
117+
}
118+
let lhs_refs = cx.typeck_results().expr_ty(lhs).peel_refs();
119+
let rhs_refs = cx.typeck_results().expr_ty(rhs).peel_refs();
120+
let has_valid_assign_op = Self::has_valid_assign_op(op, rhs, rhs_refs);
121+
if has_valid_assign_op || Self::has_valid_bin_op(lhs, lhs_refs, rhs, rhs_refs) {
122+
return;
123+
}
124+
self.issue_lint(cx, expr);
125+
}
53126
}
54127

55128
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
56129
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
57-
if self.expr_span.is_some() {
58-
return;
59-
}
60-
if let Some(span) = self.const_span && span.contains(expr.span) {
130+
if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
61131
return;
62132
}
63133
match &expr.kind {
64134
hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
65-
let (
66-
hir::BinOpKind::Add
67-
| hir::BinOpKind::Sub
68-
| hir::BinOpKind::Mul
69-
| hir::BinOpKind::Div
70-
| hir::BinOpKind::Rem
71-
| hir::BinOpKind::Shl
72-
| hir::BinOpKind::Shr
73-
) = op.node else {
74-
return;
75-
};
76-
if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
77-
return;
78-
}
79-
self.issue_lint(cx, expr);
135+
self.manage_bin_ops(cx, expr, op, lhs, rhs);
80136
},
81137
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
82-
// CTFE already takes care of things like `-1` that do not overflow.
83138
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
84139
self.issue_lint(cx, expr);
85140
}
@@ -89,16 +144,15 @@ impl<'tcx> LateLintPass<'tcx> for Arithmetic {
89144
}
90145

91146
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
92-
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
93-
match cx.tcx.hir().body_owner_kind(body_owner) {
94-
hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => {
95-
let body_span = cx.tcx.def_span(body_owner);
96-
if let Some(span) = self.const_span && span.contains(body_span) {
97-
return;
98-
}
99-
self.const_span = Some(body_span);
100-
},
101-
hir::BodyOwnerKind::Closure | hir::BodyOwnerKind::Fn => {},
147+
let body_owner = cx.tcx.hir().body_owner(body.id());
148+
let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
149+
let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
150+
if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
151+
let body_span = cx.tcx.hir().span_with_body(body_owner);
152+
if let Some(span) = self.const_span && span.contains(body_span) {
153+
return;
154+
}
155+
self.const_span = Some(body_span);
102156
}
103157
}
104158

clippy_lints/src/operators/mod.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,29 @@ declare_clippy_lint! {
6161

6262
declare_clippy_lint! {
6363
/// ### What it does
64-
/// Checks for any kind of arithmetic operation of any type.
64+
/// Checks any kind of arithmetic operation of any type.
6565
///
6666
/// Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust
6767
/// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
68-
/// or can panic (`/`, `%`). Known safe built-in types like `Wrapping` or `Saturing` are filtered
69-
/// away.
68+
/// or can panic (`/`, `%`).
69+
///
70+
/// Known safe built-in types like `Wrapping` or `Saturing`, floats, operations in constant
71+
/// environments, allowed types and non-constant operations that won't overflow are ignored.
7072
///
7173
/// ### Why is this bad?
72-
/// Integer overflow will trigger a panic in debug builds or will wrap in
73-
/// release mode. Division by zero will cause a panic in either mode. In some applications one
74-
/// wants explicitly checked, wrapping or saturating arithmetic.
74+
/// For integers, overflow will trigger a panic in debug builds or wrap the result in
75+
/// release mode; division by zero will cause a panic in either mode. As a result, it is
76+
/// desirable to explicitly call checked, wrapping or saturating arithmetic methods.
7577
///
7678
/// #### Example
7779
/// ```rust
78-
/// # let a = 0;
79-
/// a + 1;
80+
/// // `n` can be any number, including `i32::MAX`.
81+
/// fn foo(n: i32) -> i32 {
82+
/// n + 1
83+
/// }
8084
/// ```
8185
///
82-
/// Third-party types also tend to overflow.
86+
/// Third-party types can also overflow or present unwanted side-effects.
8387
///
8488
/// #### Example
8589
/// ```ignore,rust

src/docs/arithmetic.txt

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
### What it does
2-
Checks for any kind of arithmetic operation of any type.
2+
Checks any kind of arithmetic operation of any type.
33

44
Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust
55
Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
6-
or can panic (`/`, `%`). Known safe built-in types like `Wrapping` or `Saturing` are filtered
7-
away.
6+
or can panic (`/`, `%`).
7+
8+
Known safe built-in types like `Wrapping` or `Saturing`, floats, operations in constant
9+
environments, allowed types and non-constant operations that won't overflow are ignored.
810

911
### Why is this bad?
10-
Integer overflow will trigger a panic in debug builds or will wrap in
11-
release mode. Division by zero will cause a panic in either mode. In some applications one
12-
wants explicitly checked, wrapping or saturating arithmetic.
12+
For integers, overflow will trigger a panic in debug builds or wrap the result in
13+
release mode; division by zero will cause a panic in either mode. As a result, it is
14+
desirable to explicitly call checked, wrapping or saturating arithmetic methods.
1315

1416
#### Example
1517
```
16-
a + 1;
18+
// `n` can be any number, including `i32::MAX`.
19+
fn foo(n: i32) -> i32 {
20+
n + 1
21+
}
1722
```
1823

19-
Third-party types also tend to overflow.
24+
Third-party types can also overflow or present unwanted side-effects.
2025

2126
#### Example
2227
```

tests/ui/arithmetic.fixed

-27
This file was deleted.

tests/ui/arithmetic.rs

+34-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
// run-rustfix
2-
3-
#![allow(clippy::unnecessary_owned_empty_strings)]
4-
#![feature(saturating_int_impl)]
1+
#![allow(clippy::assign_op_pattern, clippy::unnecessary_owned_empty_strings)]
2+
#![feature(inline_const, saturating_int_impl)]
53
#![warn(clippy::arithmetic)]
64

75
use core::num::{Saturating, Wrapping};
86

97
pub fn hard_coded_allowed() {
8+
let _ = 1f32 + 1f32;
9+
let _ = 1f64 + 1f64;
10+
1011
let _ = Saturating(0u32) + Saturating(0u32);
1112
let _ = String::new() + "";
1213
let _ = Wrapping(0u32) + Wrapping(0u32);
@@ -24,4 +25,33 @@ pub fn hard_coded_allowed() {
2425
let _ = inferred_wrapping + inferred_wrapping;
2526
}
2627

28+
#[rustfmt::skip]
29+
pub fn non_overflowing_ops() {
30+
const _: i32 = { let mut n = 1; n += 1; n };
31+
let _ = const { let mut n = 1; n += 1; n };
32+
33+
const _: i32 = { let mut n = 1; n = n + 1; n };
34+
let _ = const { let mut n = 1; n = n + 1; n };
35+
36+
const _: i32 = { let mut n = 1; n = 1 + n; n };
37+
let _ = const { let mut n = 1; n = 1 + n; n };
38+
39+
const _: i32 = 1 + 1;
40+
let _ = 1 + 1;
41+
let _ = const { 1 + 1 };
42+
43+
let mut _a = 1;
44+
_a *= 1;
45+
_a /= 1;
46+
}
47+
48+
#[rustfmt::skip]
49+
pub fn overflowing_ops() {
50+
let mut _a = 1; _a += 1;
51+
52+
let mut _b = 1; _b = _b + 1;
53+
54+
let mut _c = 1; _c = 1 + _c;
55+
}
56+
2757
fn main() {}

tests/ui/arithmetic.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: arithmetic detected
2+
--> $DIR/arithmetic.rs:50:21
3+
|
4+
LL | let mut _a = 1; _a += 1;
5+
| ^^^^^^^
6+
|
7+
= note: `-D clippy::arithmetic` implied by `-D warnings`
8+
9+
error: arithmetic detected
10+
--> $DIR/arithmetic.rs:52:26
11+
|
12+
LL | let mut _b = 1; _b = _b + 1;
13+
| ^^^^^^
14+
15+
error: arithmetic detected
16+
--> $DIR/arithmetic.rs:54:26
17+
|
18+
LL | let mut _c = 1; _c = 1 + _c;
19+
| ^^^^^^
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)