]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / operators / arithmetic_side_effects.rs
CommitLineData
f2b60f7d 1use super::ARITHMETIC_SIDE_EFFECTS;
487cf647 2use clippy_utils::{
9ffffee4 3 consts::{constant, constant_simple, Constant},
487cf647 4 diagnostics::span_lint,
9ffffee4 5 is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary,
487cf647 6};
f2b60f7d 7use rustc_ast as ast;
9c376795 8use rustc_data_structures::fx::{FxHashMap, FxHashSet};
f2b60f7d
FG
9use rustc_hir as hir;
10use rustc_lint::{LateContext, LateLintPass};
11use rustc_middle::ty::Ty;
12use rustc_session::impl_lint_pass;
13use rustc_span::source_map::{Span, Spanned};
14
9c376795
FG
15const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[
16 ["f32", "f32"],
17 ["f64", "f64"],
18 ["std::num::Saturating", "std::num::Saturating"],
19 ["std::num::Wrapping", "std::num::Wrapping"],
20 ["std::string::String", "&str"],
f2b60f7d 21];
9c376795 22const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
f2b60f7d
FG
23
24#[derive(Debug)]
25pub struct ArithmeticSideEffects {
9c376795
FG
26 allowed_binary: FxHashMap<String, FxHashSet<String>>,
27 allowed_unary: FxHashSet<String>,
f2b60f7d
FG
28 // Used to check whether expressions are constants, such as in enum discriminants and consts
29 const_span: Option<Span>,
30 expr_span: Option<Span>,
31}
32
33impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
34
35impl ArithmeticSideEffects {
36 #[must_use]
9c376795
FG
37 pub fn new(user_allowed_binary: Vec<[String; 2]>, user_allowed_unary: Vec<String>) -> Self {
38 let mut allowed_binary: FxHashMap<String, FxHashSet<String>> = <_>::default();
39 for [lhs, rhs] in user_allowed_binary.into_iter().chain(
40 HARD_CODED_ALLOWED_BINARY
41 .iter()
42 .copied()
43 .map(|[lhs, rhs]| [lhs.to_string(), rhs.to_string()]),
44 ) {
45 allowed_binary.entry(lhs).or_default().insert(rhs);
46 }
47 let allowed_unary = user_allowed_unary
48 .into_iter()
49 .chain(HARD_CODED_ALLOWED_UNARY.iter().copied().map(String::from))
50 .collect();
f2b60f7d 51 Self {
9c376795
FG
52 allowed_binary,
53 allowed_unary,
f2b60f7d
FG
54 const_span: None,
55 expr_span: None,
56 }
57 }
58
9c376795
FG
59 /// Checks if the lhs and the rhs types of a binary operation like "addition" or
60 /// "multiplication" are present in the inner set of allowed types.
61 fn has_allowed_binary(&self, lhs_ty: Ty<'_>, rhs_ty: Ty<'_>) -> bool {
62 let lhs_ty_string = lhs_ty.to_string();
63 let lhs_ty_string_elem = lhs_ty_string.split('<').next().unwrap_or_default();
64 let rhs_ty_string = rhs_ty.to_string();
65 let rhs_ty_string_elem = rhs_ty_string.split('<').next().unwrap_or_default();
66 if let Some(rhs_from_specific) = self.allowed_binary.get(lhs_ty_string_elem)
67 && {
68 let rhs_has_allowed_ty = rhs_from_specific.contains(rhs_ty_string_elem);
69 rhs_has_allowed_ty || rhs_from_specific.contains("*")
70 }
71 {
72 true
73 } else if let Some(rhs_from_glob) = self.allowed_binary.get("*") {
74 rhs_from_glob.contains(rhs_ty_string_elem)
75 } else {
76 false
77 }
78 }
79
80 /// Checks if the type of an unary operation like "negation" is present in the inner set of
81 /// allowed types.
82 fn has_allowed_unary(&self, ty: Ty<'_>) -> bool {
83 let ty_string = ty.to_string();
84 let ty_string_elem = ty_string.split('<').next().unwrap_or_default();
85 self.allowed_unary.contains(ty_string_elem)
f2b60f7d
FG
86 }
87
2b03887a
FG
88 // For example, 8i32 or &i64::MAX.
89 fn is_integral(ty: Ty<'_>) -> bool {
90 ty.peel_refs().is_integral()
f2b60f7d
FG
91 }
92
2b03887a 93 // Common entry-point to avoid code duplication.
f2b60f7d 94 fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
2b03887a
FG
95 let msg = "arithmetic operation that can potentially result in unexpected side-effects";
96 span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
f2b60f7d
FG
97 self.expr_span = Some(expr.span);
98 }
99
9ffffee4 100 /// Returns the numeric value of a literal integer originated from `expr`, if any.
9c376795 101 ///
9ffffee4
FG
102 /// Literal integers can be originated from adhoc declarations like `1`, associated constants
103 /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`,
104 fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> {
9c376795
FG
105 let actual = peel_hir_expr_unary(expr).0;
106 if let hir::ExprKind::Lit(ref lit) = actual.kind && let ast::LitKind::Int(n, _) = lit.node {
9ffffee4 107 return Some(n)
2b03887a 108 }
9ffffee4
FG
109 if let Some((Constant::Int(n), _)) = constant(cx, cx.typeck_results(), expr) {
110 return Some(n);
2b03887a 111 }
9ffffee4 112 None
2b03887a
FG
113 }
114
f2b60f7d
FG
115 /// Manages when the lint should be triggered. Operations in constant environments, hard coded
116 /// types, custom allowed types and non-constant operations that won't overflow are ignored.
2b03887a 117 fn manage_bin_ops<'tcx>(
f2b60f7d 118 &mut self,
2b03887a
FG
119 cx: &LateContext<'tcx>,
120 expr: &hir::Expr<'tcx>,
f2b60f7d 121 op: &Spanned<hir::BinOpKind>,
2b03887a
FG
122 lhs: &hir::Expr<'tcx>,
123 rhs: &hir::Expr<'tcx>,
f2b60f7d
FG
124 ) {
125 if constant_simple(cx, cx.typeck_results(), expr).is_some() {
126 return;
127 }
128 if !matches!(
129 op.node,
130 hir::BinOpKind::Add
f2b60f7d 131 | hir::BinOpKind::Div
9c376795 132 | hir::BinOpKind::Mul
f2b60f7d
FG
133 | hir::BinOpKind::Rem
134 | hir::BinOpKind::Shl
135 | hir::BinOpKind::Shr
9c376795 136 | hir::BinOpKind::Sub
f2b60f7d
FG
137 ) {
138 return;
139 };
2b03887a
FG
140 let lhs_ty = cx.typeck_results().expr_ty(lhs);
141 let rhs_ty = cx.typeck_results().expr_ty(rhs);
9c376795 142 if self.has_allowed_binary(lhs_ty, rhs_ty) {
f2b60f7d
FG
143 return;
144 }
2b03887a 145 let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
353b0b11
FG
146 if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node {
147 // At least for integers, shifts are already handled by the CTFE
148 return;
149 }
487cf647
FG
150 let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
151 let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
9ffffee4
FG
152 match (
153 Self::literal_integer(cx, actual_lhs),
154 Self::literal_integer(cx, actual_rhs),
155 ) {
487cf647 156 (None, None) => false,
353b0b11
FG
157 (None, Some(n)) => match (&op.node, n) {
158 // Division and module are always valid if applied to non-zero integers
159 (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
160 // Adding or subtracting zeros is always a no-op
161 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
162 // Multiplication by 1 or 0 will never overflow
163 | (hir::BinOpKind::Mul, 0 | 1)
164 => true,
165 _ => false,
166 },
167 (Some(n), None) => match (&op.node, n) {
168 // Adding or subtracting zeros is always a no-op
487cf647 169 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
353b0b11
FG
170 // Multiplication by 1 or 0 will never overflow
171 | (hir::BinOpKind::Mul, 0 | 1)
172 => true,
487cf647
FG
173 _ => false,
174 },
175 (Some(_), Some(_)) => {
176 matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
177 },
2b03887a
FG
178 }
179 } else {
180 false
181 };
182 if !has_valid_op {
183 self.issue_lint(cx, expr);
f2b60f7d 184 }
f2b60f7d 185 }
487cf647
FG
186
187 fn manage_unary_ops<'tcx>(
188 &mut self,
189 cx: &LateContext<'tcx>,
190 expr: &hir::Expr<'tcx>,
191 un_expr: &hir::Expr<'tcx>,
192 un_op: hir::UnOp,
193 ) {
194 let hir::UnOp::Neg = un_op else { return; };
195 if constant(cx, cx.typeck_results(), un_expr).is_some() {
196 return;
197 }
198 let ty = cx.typeck_results().expr_ty(expr).peel_refs();
9c376795 199 if self.has_allowed_unary(ty) {
487cf647
FG
200 return;
201 }
202 let actual_un_expr = peel_hir_expr_refs(un_expr).0;
9ffffee4 203 if Self::literal_integer(cx, actual_un_expr).is_some() {
487cf647
FG
204 return;
205 }
206 self.issue_lint(cx, expr);
207 }
208
9ffffee4
FG
209 fn should_skip_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
210 is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id)
211 || self.expr_span.is_some()
212 || self.const_span.map_or(false, |sp| sp.contains(expr.span))
487cf647 213 }
f2b60f7d
FG
214}
215
216impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
2b03887a 217 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
9ffffee4 218 if self.should_skip_expr(cx, expr) {
f2b60f7d
FG
219 return;
220 }
221 match &expr.kind {
487cf647 222 hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
f2b60f7d
FG
223 self.manage_bin_ops(cx, expr, op, lhs, rhs);
224 },
487cf647
FG
225 hir::ExprKind::Unary(un_op, un_expr) => {
226 self.manage_unary_ops(cx, expr, un_expr, *un_op);
f2b60f7d
FG
227 },
228 _ => {},
229 }
230 }
231
232 fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
233 let body_owner = cx.tcx.hir().body_owner(body.id());
9ffffee4
FG
234 let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id());
235
f2b60f7d
FG
236 let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
237 if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
238 let body_span = cx.tcx.hir().span_with_body(body_owner);
239 if let Some(span) = self.const_span && span.contains(body_span) {
240 return;
241 }
242 self.const_span = Some(body_span);
243 }
244 }
245
246 fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
247 let body_owner = cx.tcx.hir().body_owner(body.id());
248 let body_span = cx.tcx.hir().span(body_owner);
249 if let Some(span) = self.const_span && span.contains(body_span) {
250 return;
251 }
252 self.const_span = None;
253 }
254
255 fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
256 if Some(expr.span) == self.expr_span {
257 self.expr_span = None;
258 }
259 }
260}