]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
Rollup merge of #104901 - krtab:filetype_compare, r=the8472
[rust.git] / src / tools / clippy / clippy_lints / src / operators / arithmetic_side_effects.rs
1 use super::ARITHMETIC_SIDE_EFFECTS;
2 use clippy_utils::{
3     consts::{constant, constant_simple},
4     diagnostics::span_lint,
5     peel_hir_expr_refs,
6 };
7 use rustc_ast as ast;
8 use rustc_data_structures::fx::FxHashSet;
9 use rustc_hir as hir;
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_middle::ty::Ty;
12 use rustc_session::impl_lint_pass;
13 use rustc_span::source_map::{Span, Spanned};
14
15 const HARD_CODED_ALLOWED: &[&str] = &[
16     "&str",
17     "f32",
18     "f64",
19     "std::num::Saturating",
20     "std::num::Wrapping",
21     "std::string::String",
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     /// Checks if the given `expr` has any of the inner `allowed` elements.
46     fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
47         self.allowed
48             .contains(ty.to_string().split('<').next().unwrap_or_default())
49     }
50
51     // For example, 8i32 or &i64::MAX.
52     fn is_integral(ty: Ty<'_>) -> bool {
53         ty.peel_refs().is_integral()
54     }
55
56     // Common entry-point to avoid code duplication.
57     fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
58         let msg = "arithmetic operation that can potentially result in unexpected side-effects";
59         span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
60         self.expr_span = Some(expr.span);
61     }
62
63     /// If `expr` is not a literal integer like `1`, returns `None`.
64     fn literal_integer(expr: &hir::Expr<'_>) -> Option<u128> {
65         if let hir::ExprKind::Lit(ref lit) = expr.kind && let ast::LitKind::Int(n, _) = lit.node {
66             Some(n)
67         }
68         else {
69             None
70         }
71     }
72
73     /// Manages when the lint should be triggered. Operations in constant environments, hard coded
74     /// types, custom allowed types and non-constant operations that won't overflow are ignored.
75     fn manage_bin_ops<'tcx>(
76         &mut self,
77         cx: &LateContext<'tcx>,
78         expr: &hir::Expr<'tcx>,
79         op: &Spanned<hir::BinOpKind>,
80         lhs: &hir::Expr<'tcx>,
81         rhs: &hir::Expr<'tcx>,
82     ) {
83         if constant_simple(cx, cx.typeck_results(), expr).is_some() {
84             return;
85         }
86         if !matches!(
87             op.node,
88             hir::BinOpKind::Add
89                 | hir::BinOpKind::Sub
90                 | hir::BinOpKind::Mul
91                 | hir::BinOpKind::Div
92                 | hir::BinOpKind::Rem
93                 | hir::BinOpKind::Shl
94                 | hir::BinOpKind::Shr
95         ) {
96             return;
97         };
98         let lhs_ty = cx.typeck_results().expr_ty(lhs);
99         let rhs_ty = cx.typeck_results().expr_ty(rhs);
100         let lhs_and_rhs_have_the_same_ty = lhs_ty == rhs_ty;
101         if lhs_and_rhs_have_the_same_ty && self.is_allowed_ty(lhs_ty) && self.is_allowed_ty(rhs_ty) {
102             return;
103         }
104         let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
105             let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
106             let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
107             match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) {
108                 (None, None) => false,
109                 (None, Some(n)) | (Some(n), None) => match (&op.node, n) {
110                     (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
111                     (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
112                     | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
113                     | (hir::BinOpKind::Mul, 0 | 1) => true,
114                     _ => false,
115                 },
116                 (Some(_), Some(_)) => {
117                     matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
118                 },
119             }
120         } else {
121             false
122         };
123         if !has_valid_op {
124             self.issue_lint(cx, expr);
125         }
126     }
127
128     fn manage_unary_ops<'tcx>(
129         &mut self,
130         cx: &LateContext<'tcx>,
131         expr: &hir::Expr<'tcx>,
132         un_expr: &hir::Expr<'tcx>,
133         un_op: hir::UnOp,
134     ) {
135         let hir::UnOp::Neg = un_op else { return; };
136         if constant(cx, cx.typeck_results(), un_expr).is_some() {
137             return;
138         }
139         let ty = cx.typeck_results().expr_ty(expr).peel_refs();
140         if self.is_allowed_ty(ty) {
141             return;
142         }
143         let actual_un_expr = peel_hir_expr_refs(un_expr).0;
144         if Self::literal_integer(actual_un_expr).is_some() {
145             return;
146         }
147         self.issue_lint(cx, expr);
148     }
149
150     fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool {
151         self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span))
152     }
153 }
154
155 impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
156     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
157         if self.should_skip_expr(expr) {
158             return;
159         }
160         match &expr.kind {
161             hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
162                 self.manage_bin_ops(cx, expr, op, lhs, rhs);
163             },
164             hir::ExprKind::Unary(un_op, un_expr) => {
165                 self.manage_unary_ops(cx, expr, un_expr, *un_op);
166             },
167             _ => {},
168         }
169     }
170
171     fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
172         let body_owner = cx.tcx.hir().body_owner(body.id());
173         let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
174         let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
175         if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
176             let body_span = cx.tcx.hir().span_with_body(body_owner);
177             if let Some(span) = self.const_span && span.contains(body_span) {
178                 return;
179             }
180             self.const_span = Some(body_span);
181         }
182     }
183
184     fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
185         let body_owner = cx.tcx.hir().body_owner(body.id());
186         let body_span = cx.tcx.hir().span(body_owner);
187         if let Some(span) = self.const_span && span.contains(body_span) {
188             return;
189         }
190         self.const_span = None;
191     }
192
193     fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
194         if Some(expr.span) == self.expr_span {
195             self.expr_span = None;
196         }
197     }
198 }