Skip to content

Commit 98b343c

Browse files
committed
[arithmetic-side-effects] Detect overflowing associated constants of integers
1 parent d15e5e6 commit 98b343c

File tree

4 files changed

+130
-114
lines changed

4 files changed

+130
-114
lines changed

clippy_lints/src/operators/arithmetic_side_effects.rs

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
use super::ARITHMETIC_SIDE_EFFECTS;
2-
use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
2+
use clippy_utils::{
3+
consts::{constant, constant_simple},
4+
diagnostics::span_lint,
5+
peel_hir_expr_refs,
6+
};
37
use rustc_ast as ast;
48
use rustc_data_structures::fx::FxHashSet;
59
use rustc_hir as hir;
610
use rustc_lint::{LateContext, LateLintPass};
711
use rustc_middle::ty::Ty;
812
use rustc_session::impl_lint_pass;
9-
use rustc_span::source_map::{Span, Spanned};
13+
use rustc_span::{
14+
source_map::{Span, Spanned},
15+
sym,
16+
};
1017

1118
const HARD_CODED_ALLOWED: &[&str] = &[
1219
"&str",
@@ -38,24 +45,6 @@ impl ArithmeticSideEffects {
3845
}
3946
}
4047

41-
/// Assuming that `expr` is a literal integer, checks operators (+=, -=, *, /) in a
42-
/// non-constant environment that won't overflow.
43-
fn has_valid_op(op: &Spanned<hir::BinOpKind>, expr: &hir::Expr<'_>) -> bool {
44-
if let hir::ExprKind::Lit(ref lit) = expr.kind &&
45-
let ast::LitKind::Int(value, _) = lit.node
46-
{
47-
match (&op.node, value) {
48-
(hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
49-
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
50-
| (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
51-
| (hir::BinOpKind::Mul, 0 | 1) => true,
52-
_ => false,
53-
}
54-
} else {
55-
false
56-
}
57-
}
58-
5948
/// Checks if the given `expr` has any of the inner `allowed` elements.
6049
fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
6150
self.allowed
@@ -74,15 +63,14 @@ impl ArithmeticSideEffects {
7463
self.expr_span = Some(expr.span);
7564
}
7665

77-
/// If `expr` does not match any variant of `LiteralIntegerTy`, returns `None`.
78-
fn literal_integer<'expr, 'tcx>(expr: &'expr hir::Expr<'tcx>) -> Option<LiteralIntegerTy<'expr, 'tcx>> {
79-
if matches!(expr.kind, hir::ExprKind::Lit(_)) {
80-
return Some(LiteralIntegerTy::Value(expr));
66+
/// If `expr` is not a literal integer like `1`, returns `None`.
67+
fn literal_integer(expr: &hir::Expr<'_>) -> Option<u128> {
68+
if let hir::ExprKind::Lit(ref lit) = expr.kind && let ast::LitKind::Int(n, _) = lit.node {
69+
Some(n)
8170
}
82-
if let hir::ExprKind::AddrOf(.., inn) = expr.kind && let hir::ExprKind::Lit(_) = inn.kind {
83-
return Some(LiteralIntegerTy::Ref(inn));
71+
else {
72+
None
8473
}
85-
None
8674
}
8775

8876
/// Manages when the lint should be triggered. Operations in constant environments, hard coded
@@ -117,10 +105,20 @@ impl ArithmeticSideEffects {
117105
return;
118106
}
119107
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
120-
match (Self::literal_integer(lhs), Self::literal_integer(rhs)) {
121-
(None, Some(lit_int_ty)) | (Some(lit_int_ty), None) => Self::has_valid_op(op, lit_int_ty.into()),
122-
(Some(LiteralIntegerTy::Value(_)), Some(LiteralIntegerTy::Value(_))) => true,
123-
(None, None) | (Some(_), Some(_)) => false,
108+
let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
109+
let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
110+
match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) {
111+
(None, None) => false,
112+
(None, Some(n)) | (Some(n), None) => match (&op.node, n) {
113+
(hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
114+
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
115+
| (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
116+
| (hir::BinOpKind::Mul, 0 | 1) => true,
117+
_ => false,
118+
},
119+
(Some(_), Some(_)) => {
120+
matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
121+
},
124122
}
125123
} else {
126124
false
@@ -129,21 +127,45 @@ impl ArithmeticSideEffects {
129127
self.issue_lint(cx, expr);
130128
}
131129
}
130+
131+
fn manage_unary_ops<'tcx>(
132+
&mut self,
133+
cx: &LateContext<'tcx>,
134+
expr: &hir::Expr<'tcx>,
135+
un_expr: &hir::Expr<'tcx>,
136+
un_op: hir::UnOp,
137+
) {
138+
let hir::UnOp::Neg = un_op else { return; };
139+
if constant(cx, cx.typeck_results(), un_expr).is_some() {
140+
return;
141+
}
142+
let ty = cx.typeck_results().expr_ty(expr).peel_refs();
143+
if self.is_allowed_ty(ty) {
144+
return;
145+
}
146+
let actual_un_expr = peel_hir_expr_refs(un_expr).0;
147+
if Self::literal_integer(actual_un_expr).is_some() {
148+
return;
149+
}
150+
self.issue_lint(cx, expr);
151+
}
152+
153+
fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool {
154+
self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span))
155+
}
132156
}
133157

134158
impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
135159
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
136-
if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
160+
if self.should_skip_expr(expr) {
137161
return;
138162
}
139163
match &expr.kind {
140-
hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
164+
hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
141165
self.manage_bin_ops(cx, expr, op, lhs, rhs);
142166
},
143-
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
144-
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
145-
self.issue_lint(cx, expr);
146-
}
167+
hir::ExprKind::Unary(un_op, un_expr) => {
168+
self.manage_unary_ops(cx, expr, un_expr, *un_op);
147169
},
148170
_ => {},
149171
}
@@ -177,22 +199,3 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
177199
}
178200
}
179201
}
180-
181-
/// Tells if an expression is a integer declared by value or by reference.
182-
///
183-
/// If `LiteralIntegerTy::Ref`, then the contained value will be `hir::ExprKind::Lit` rather
184-
/// than `hirExprKind::Addr`.
185-
enum LiteralIntegerTy<'expr, 'tcx> {
186-
/// For example, `&199`
187-
Ref(&'expr hir::Expr<'tcx>),
188-
/// For example, `1` or `i32::MAX`
189-
Value(&'expr hir::Expr<'tcx>),
190-
}
191-
192-
impl<'expr, 'tcx> From<LiteralIntegerTy<'expr, 'tcx>> for &'expr hir::Expr<'tcx> {
193-
fn from(from: LiteralIntegerTy<'expr, 'tcx>) -> Self {
194-
match from {
195-
LiteralIntegerTy::Ref(elem) | LiteralIntegerTy::Value(elem) => elem,
196-
}
197-
}
198-
}

tests/ui-toml/arithmetic_side_effects_allowed/arithmetic_side_effects_allowed.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::arithmetic_side_effects)]
22

3-
use core::ops::Add;
3+
use core::ops::{Add, Neg};
44

55
#[derive(Clone, Copy)]
66
struct Point {
@@ -16,9 +16,18 @@ impl Add for Point {
1616
}
1717
}
1818

19+
impl Neg for Point {
20+
type Output = Self;
21+
22+
fn neg(self) -> Self::Output {
23+
todo!()
24+
}
25+
}
26+
1927
fn main() {
2028
let _ = Point { x: 1, y: 0 } + Point { x: 2, y: 3 };
2129

2230
let point: Point = Point { x: 1, y: 0 };
2331
let _ = point + point;
32+
let _ = -point;
2433
}

tests/ui/arithmetic_side_effects.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,12 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri
150150
_n = 23 + 85;
151151

152152
// Unary
153-
_n = -1;
154-
_n = -(-1);
153+
_n = -2147483647;
154+
_n = -i32::MAX;
155+
_n = -i32::MIN;
156+
_n = -&2147483647;
157+
_n = -&i32::MAX;
158+
_n = -&i32::MIN;
155159
}
156160

157161
pub fn runtime_ops() {

0 commit comments

Comments
 (0)