]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
Auto merge of #101969 - reez12g:issue-101306, r=reez12g
[rust.git] / src / tools / clippy / clippy_lints / src / operators / arithmetic_side_effects.rs
1 #![allow(
2     // False positive
3     clippy::match_same_arms
4 )]
5
6 use super::ARITHMETIC_SIDE_EFFECTS;
7 use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
8 use rustc_ast as ast;
9 use rustc_data_structures::fx::FxHashSet;
10 use rustc_hir as hir;
11 use rustc_lint::{LateContext, LateLintPass};
12 use rustc_middle::ty::Ty;
13 use rustc_session::impl_lint_pass;
14 use rustc_span::source_map::{Span, Spanned};
15
16 const HARD_CODED_ALLOWED: &[&str] = &[
17     "f32",
18     "f64",
19     "std::num::Saturating",
20     "std::string::String",
21     "std::num::Wrapping",
22 ];
23
24 #[derive(Debug)]
25 pub struct ArithmeticSideEffects {
26     allowed: FxHashSet<String>,
27     // Used to check whether expressions are constants, such as in enum discriminants and consts
28     const_span: Option<Span>,
29     expr_span: Option<Span>,
30 }
31
32 impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
33
34 impl ArithmeticSideEffects {
35     #[must_use]
36     pub fn new(mut allowed: FxHashSet<String>) -> Self {
37         allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
38         Self {
39             allowed,
40             const_span: None,
41             expr_span: None,
42         }
43     }
44
45     /// Assuming that `expr` is a literal integer, checks operators (+=, -=, *, /) in a
46     /// non-constant environment that won't overflow.
47     fn has_valid_op(op: &Spanned<hir::BinOpKind>, expr: &hir::Expr<'_>) -> bool {
48         if let hir::BinOpKind::Add | hir::BinOpKind::Sub = op.node
49             && let hir::ExprKind::Lit(ref lit) = expr.kind
50             && let ast::LitKind::Int(0, _) = lit.node
51         {
52             return true;
53         }
54         if let hir::BinOpKind::Div | hir::BinOpKind::Rem = op.node
55             && let hir::ExprKind::Lit(ref lit) = expr.kind
56             && !matches!(lit.node, ast::LitKind::Int(0, _))
57         {
58             return true;
59         }
60         if let hir::BinOpKind::Mul = op.node
61             && let hir::ExprKind::Lit(ref lit) = expr.kind
62             && let ast::LitKind::Int(0 | 1, _) = lit.node
63         {
64             return true;
65         }
66         false
67     }
68
69     /// Checks if the given `expr` has any of the inner `allowed` elements.
70     fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
71         self.allowed.contains(
72             cx.typeck_results()
73                 .expr_ty(expr)
74                 .to_string()
75                 .split('<')
76                 .next()
77                 .unwrap_or_default(),
78         )
79     }
80
81     /// Explicit integers like `1` or `i32::MAX`. Does not take into consideration references.
82     fn is_literal_integer(expr: &hir::Expr<'_>, expr_refs: Ty<'_>) -> bool {
83         let is_integral = expr_refs.is_integral();
84         let is_literal = matches!(expr.kind, hir::ExprKind::Lit(_));
85         is_integral && is_literal
86     }
87
88     fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
89         let msg = "arithmetic operation that can potentially result in unexpected side-effects";
90         span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
91         self.expr_span = Some(expr.span);
92     }
93
94     /// Manages when the lint should be triggered. Operations in constant environments, hard coded
95     /// types, custom allowed types and non-constant operations that won't overflow are ignored.
96     fn manage_bin_ops(
97         &mut self,
98         cx: &LateContext<'_>,
99         expr: &hir::Expr<'_>,
100         op: &Spanned<hir::BinOpKind>,
101         lhs: &hir::Expr<'_>,
102         rhs: &hir::Expr<'_>,
103     ) {
104         if constant_simple(cx, cx.typeck_results(), expr).is_some() {
105             return;
106         }
107         if !matches!(
108             op.node,
109             hir::BinOpKind::Add
110                 | hir::BinOpKind::Sub
111                 | hir::BinOpKind::Mul
112                 | hir::BinOpKind::Div
113                 | hir::BinOpKind::Rem
114                 | hir::BinOpKind::Shl
115                 | hir::BinOpKind::Shr
116         ) {
117             return;
118         };
119         if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
120             return;
121         }
122         let has_valid_op = match (
123             Self::is_literal_integer(lhs, cx.typeck_results().expr_ty(lhs).peel_refs()),
124             Self::is_literal_integer(rhs, cx.typeck_results().expr_ty(rhs).peel_refs()),
125         ) {
126             (true, true) => true,
127             (true, false) => Self::has_valid_op(op, lhs),
128             (false, true) => Self::has_valid_op(op, rhs),
129             (false, false) => false,
130         };
131         if !has_valid_op {
132             self.issue_lint(cx, expr);
133         }
134     }
135 }
136
137 impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
138     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
139         if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
140             return;
141         }
142         match &expr.kind {
143             hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
144                 self.manage_bin_ops(cx, expr, op, lhs, rhs);
145             },
146             hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
147                 if constant_simple(cx, cx.typeck_results(), expr).is_none() {
148                     self.issue_lint(cx, expr);
149                 }
150             },
151             _ => {},
152         }
153     }
154
155     fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
156         let body_owner = cx.tcx.hir().body_owner(body.id());
157         let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
158         let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
159         if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
160             let body_span = cx.tcx.hir().span_with_body(body_owner);
161             if let Some(span) = self.const_span && span.contains(body_span) {
162                 return;
163             }
164             self.const_span = Some(body_span);
165         }
166     }
167
168     fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
169         let body_owner = cx.tcx.hir().body_owner(body.id());
170         let body_span = cx.tcx.hir().span(body_owner);
171         if let Some(span) = self.const_span && span.contains(body_span) {
172             return;
173         }
174         self.const_span = None;
175     }
176
177     fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
178         if Some(expr.span) == self.expr_span {
179             self.expr_span = None;
180         }
181     }
182 }