Skip to content

Commit 871b75f

Browse files
committed
[Arithmetic] Consider literals
1 parent 5735a3b commit 871b75f

File tree

5 files changed

+134
-62
lines changed

5 files changed

+134
-62
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: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
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

1416
const HARD_CODED_ALLOWED: &[&str] = &["std::num::Saturating", "std::string::String", "std::num::Wrapping"];
1517

@@ -34,6 +36,29 @@ impl Arithmetic {
3436
}
3537
}
3638

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

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

55123
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
56124
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) {
125+
if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
61126
return;
62127
}
63128
match &expr.kind {
64129
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);
130+
self.manage_bin_ops(cx, expr, op, lhs, rhs);
80131
},
81132
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
82-
// CTFE already takes care of things like `-1` that do not overflow.
83133
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
84134
self.issue_lint(cx, expr);
85135
}
@@ -89,16 +139,15 @@ impl<'tcx> LateLintPass<'tcx> for Arithmetic {
89139
}
90140

91141
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 => {},
142+
let body_owner = cx.tcx.hir().body_owner(body.id());
143+
let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
144+
let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
145+
if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
146+
let body_span = cx.tcx.hir().span_with_body(body_owner);
147+
if let Some(span) = self.const_span && span.contains(body_span) {
148+
return;
149+
}
150+
self.const_span = Some(body_span);
102151
}
103152
}
104153

tests/ui/arithmetic.fixed

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

tests/ui/arithmetic.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
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};
@@ -24,4 +22,33 @@ pub fn hard_coded_allowed() {
2422
let _ = inferred_wrapping + inferred_wrapping;
2523
}
2624

25+
#[rustfmt::skip]
26+
pub fn non_overflowing_ops() {
27+
const _: i32 = { let mut n = 1; n += 1; n };
28+
let _ = const { let mut n = 1; n += 1; n };
29+
30+
const _: i32 = { let mut n = 1; n = n + 1; n };
31+
let _ = const { let mut n = 1; n = n + 1; n };
32+
33+
const _: i32 = { let mut n = 1; n = 1 + n; n };
34+
let _ = const { let mut n = 1; n = 1 + n; n };
35+
36+
const _: i32 = 1 + 1;
37+
let _ = 1 + 1;
38+
let _ = const { 1 + 1 };
39+
40+
let mut _a = 1;
41+
_a *= 1;
42+
_a /= 1;
43+
}
44+
45+
#[rustfmt::skip]
46+
pub fn overflowing_ops() {
47+
let mut _a = 1; _a += 1;
48+
49+
let mut _b = 1; _b = _b + 1;
50+
51+
let mut _c = 1; _c = 1 + _c;
52+
}
53+
2754
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:47: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:49:26
11+
|
12+
LL | let mut _b = 1; _b = _b + 1;
13+
| ^^^^^^
14+
15+
error: arithmetic detected
16+
--> $DIR/arithmetic.rs:51: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)