Skip to content

Commit 3d1bbe3

Browse files
committed
[Arithmetic] Consider literals
1 parent 5735a3b commit 3d1bbe3

File tree

6 files changed

+158
-72
lines changed

6 files changed

+158
-72
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![feature(box_patterns)]
44
#![feature(control_flow_enum)]
55
#![feature(drain_filter)]
6+
#![feature(if_let_guard)]
67
#![feature(iter_intersperse)]
78
#![feature(let_else)]
89
#![feature(lint_reasons)]

clippy_lints/src/operators/arithmetic.rs

Lines changed: 88 additions & 32 deletions
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,29 @@ 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+
match rhs.kind {
53+
hir::ExprKind::Lit(ref lit) if let ast::LitKind::Int(1, _) = lit.node => {
54+
return true;
55+
}
56+
_ => {}
57+
}
58+
}
59+
false
60+
}
61+
62+
/// Checks "raw" binary operators (+, -, *, /) of integers in a non-constant environment
63+
/// already handled by the CTFE.
64+
fn has_valid_bin_op(lhs: &hir::Expr<'_>, lhs_refs: Ty<'_>, rhs: &hir::Expr<'_>, rhs_refs: Ty<'_>) -> bool {
65+
Self::is_literal_integer(lhs, lhs_refs) && Self::is_literal_integer(rhs, rhs_refs)
66+
}
67+
3768
/// Checks if the given `expr` has any of the inner `allowed` elements.
3869
fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
3970
self.allowed.contains(
@@ -46,40 +77,66 @@ impl Arithmetic {
4677
)
4778
}
4879

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

55130
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
56131
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) {
132+
if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
61133
return;
62134
}
63135
match &expr.kind {
64136
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);
137+
self.manage_bin_ops(cx, expr, op, lhs, rhs);
80138
},
81139
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
82-
// CTFE already takes care of things like `-1` that do not overflow.
83140
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
84141
self.issue_lint(cx, expr);
85142
}
@@ -89,16 +146,15 @@ impl<'tcx> LateLintPass<'tcx> for Arithmetic {
89146
}
90147

91148
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 => {},
149+
let body_owner = cx.tcx.hir().body_owner(body.id());
150+
let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
151+
let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
152+
if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
153+
let body_span = cx.tcx.hir().span_with_body(body_owner);
154+
if let Some(span) = self.const_span && span.contains(body_span) {
155+
return;
156+
}
157+
self.const_span = Some(body_span);
102158
}
103159
}
104160

clippy_lints/src/operators/mod.rs

Lines changed: 13 additions & 9 deletions
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 built-in types like `Wrapping` or `Saturing`, 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, an overflow will trigger panics 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 explicit 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

tests/ui/arithmetic.fixed

Lines changed: 0 additions & 27 deletions
This file was deleted.

tests/ui/arithmetic.rs

Lines changed: 34 additions & 4 deletions
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

Lines changed: 22 additions & 0 deletions
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)