]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
New upstream version 1.75.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / operators / arithmetic_side_effects.rs
CommitLineData
f2b60f7d 1use super::ARITHMETIC_SIDE_EFFECTS;
add651ee
FG
2use clippy_utils::consts::{constant, constant_simple, Constant};
3use clippy_utils::diagnostics::span_lint;
781aab86 4use clippy_utils::ty::type_diagnostic_name;
add651ee 5use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary};
9c376795 6use rustc_data_structures::fx::{FxHashMap, FxHashSet};
f2b60f7d
FG
7use rustc_lint::{LateContext, LateLintPass};
8use rustc_middle::ty::Ty;
9use rustc_session::impl_lint_pass;
ed00b5ec
FG
10use rustc_span::{Span, Symbol};
11use rustc_span::source_map::Spanned;
781aab86 12use rustc_span::symbol::sym;
add651ee 13use {rustc_ast as ast, rustc_hir as hir};
f2b60f7d 14
781aab86 15const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]];
9c376795 16const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
781aab86
FG
17const INTEGER_METHODS: &[Symbol] = &[
18 sym::saturating_div,
19 sym::wrapping_div,
20 sym::wrapping_rem,
21 sym::wrapping_rem_euclid,
22];
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>,
49aad941 31 integer_methods: FxHashSet<Symbol>,
f2b60f7d
FG
32}
33
34impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
35
36impl ArithmeticSideEffects {
37 #[must_use]
9c376795
FG
38 pub fn new(user_allowed_binary: Vec<[String; 2]>, user_allowed_unary: Vec<String>) -> Self {
39 let mut allowed_binary: FxHashMap<String, FxHashSet<String>> = <_>::default();
40 for [lhs, rhs] in user_allowed_binary.into_iter().chain(
41 HARD_CODED_ALLOWED_BINARY
42 .iter()
43 .copied()
44 .map(|[lhs, rhs]| [lhs.to_string(), rhs.to_string()]),
45 ) {
46 allowed_binary.entry(lhs).or_default().insert(rhs);
47 }
48 let allowed_unary = user_allowed_unary
49 .into_iter()
50 .chain(HARD_CODED_ALLOWED_UNARY.iter().copied().map(String::from))
51 .collect();
f2b60f7d 52 Self {
9c376795
FG
53 allowed_binary,
54 allowed_unary,
f2b60f7d
FG
55 const_span: None,
56 expr_span: None,
781aab86 57 integer_methods: INTEGER_METHODS.iter().copied().collect(),
f2b60f7d
FG
58 }
59 }
60
9c376795
FG
61 /// Checks if the lhs and the rhs types of a binary operation like "addition" or
62 /// "multiplication" are present in the inner set of allowed types.
63 fn has_allowed_binary(&self, lhs_ty: Ty<'_>, rhs_ty: Ty<'_>) -> bool {
64 let lhs_ty_string = lhs_ty.to_string();
65 let lhs_ty_string_elem = lhs_ty_string.split('<').next().unwrap_or_default();
66 let rhs_ty_string = rhs_ty.to_string();
67 let rhs_ty_string_elem = rhs_ty_string.split('<').next().unwrap_or_default();
68 if let Some(rhs_from_specific) = self.allowed_binary.get(lhs_ty_string_elem)
69 && {
70 let rhs_has_allowed_ty = rhs_from_specific.contains(rhs_ty_string_elem);
71 rhs_has_allowed_ty || rhs_from_specific.contains("*")
72 }
73 {
ed00b5ec 74 true
9c376795
FG
75 } else if let Some(rhs_from_glob) = self.allowed_binary.get("*") {
76 rhs_from_glob.contains(rhs_ty_string_elem)
77 } else {
78 false
79 }
80 }
81
82 /// Checks if the type of an unary operation like "negation" is present in the inner set of
83 /// allowed types.
84 fn has_allowed_unary(&self, ty: Ty<'_>) -> bool {
85 let ty_string = ty.to_string();
86 let ty_string_elem = ty_string.split('<').next().unwrap_or_default();
87 self.allowed_unary.contains(ty_string_elem)
f2b60f7d
FG
88 }
89
781aab86
FG
90 /// Verifies built-in types that have specific allowed operations
91 fn has_specific_allowed_type_and_operation(
92 cx: &LateContext<'_>,
93 lhs_ty: Ty<'_>,
94 op: &Spanned<hir::BinOpKind>,
95 rhs_ty: Ty<'_>,
96 ) -> bool {
97 let is_div_or_rem = matches!(op.node, hir::BinOpKind::Div | hir::BinOpKind::Rem);
98 let is_non_zero_u = |symbol: Option<Symbol>| {
99 matches!(
100 symbol,
101 Some(
102 sym::NonZeroU128
103 | sym::NonZeroU16
104 | sym::NonZeroU32
105 | sym::NonZeroU64
106 | sym::NonZeroU8
107 | sym::NonZeroUsize
108 )
109 )
110 };
111 let is_sat_or_wrap = |ty: Ty<'_>| {
112 let is_sat = type_diagnostic_name(cx, ty) == Some(sym::Saturating);
113 let is_wrap = type_diagnostic_name(cx, ty) == Some(sym::Wrapping);
114 is_sat || is_wrap
115 };
116
117 // If the RHS is NonZeroU*, then division or module by zero will never occur
118 if is_non_zero_u(type_diagnostic_name(cx, rhs_ty)) && is_div_or_rem {
119 return true;
120 }
121 // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module
122 if is_sat_or_wrap(lhs_ty) {
123 return !is_div_or_rem;
124 }
125
126 false
127 }
128
2b03887a
FG
129 // For example, 8i32 or &i64::MAX.
130 fn is_integral(ty: Ty<'_>) -> bool {
131 ty.peel_refs().is_integral()
f2b60f7d
FG
132 }
133
2b03887a 134 // Common entry-point to avoid code duplication.
ed00b5ec
FG
135 fn issue_lint<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
136 if is_from_proc_macro(cx, expr) {
137 return;
138 }
139
2b03887a
FG
140 let msg = "arithmetic operation that can potentially result in unexpected side-effects";
141 span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
f2b60f7d
FG
142 self.expr_span = Some(expr.span);
143 }
144
9ffffee4 145 /// Returns the numeric value of a literal integer originated from `expr`, if any.
9c376795 146 ///
9ffffee4
FG
147 /// Literal integers can be originated from adhoc declarations like `1`, associated constants
148 /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`,
149 fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> {
9c376795 150 let actual = peel_hir_expr_unary(expr).0;
ed00b5ec
FG
151 if let hir::ExprKind::Lit(lit) = actual.kind
152 && let ast::LitKind::Int(n, _) = lit.node
153 {
154 return Some(n);
2b03887a 155 }
49aad941 156 if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), expr) {
9ffffee4 157 return Some(n);
2b03887a 158 }
9ffffee4 159 None
2b03887a
FG
160 }
161
f2b60f7d
FG
162 /// Manages when the lint should be triggered. Operations in constant environments, hard coded
163 /// types, custom allowed types and non-constant operations that won't overflow are ignored.
2b03887a 164 fn manage_bin_ops<'tcx>(
f2b60f7d 165 &mut self,
2b03887a 166 cx: &LateContext<'tcx>,
ed00b5ec 167 expr: &'tcx hir::Expr<'_>,
f2b60f7d 168 op: &Spanned<hir::BinOpKind>,
ed00b5ec
FG
169 lhs: &'tcx hir::Expr<'_>,
170 rhs: &'tcx hir::Expr<'_>,
f2b60f7d
FG
171 ) {
172 if constant_simple(cx, cx.typeck_results(), expr).is_some() {
173 return;
174 }
175 if !matches!(
176 op.node,
177 hir::BinOpKind::Add
f2b60f7d 178 | hir::BinOpKind::Div
9c376795 179 | hir::BinOpKind::Mul
f2b60f7d
FG
180 | hir::BinOpKind::Rem
181 | hir::BinOpKind::Shl
182 | hir::BinOpKind::Shr
9c376795 183 | hir::BinOpKind::Sub
f2b60f7d
FG
184 ) {
185 return;
186 };
add651ee
FG
187 let (mut actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
188 let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
189 actual_lhs = expr_or_init(cx, actual_lhs);
190 actual_rhs = expr_or_init(cx, actual_rhs);
49aad941
FG
191 let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs();
192 let rhs_ty = cx.typeck_results().expr_ty(actual_rhs).peel_refs();
9c376795 193 if self.has_allowed_binary(lhs_ty, rhs_ty) {
f2b60f7d
FG
194 return;
195 }
781aab86
FG
196 if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) {
197 return;
198 }
2b03887a 199 let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
353b0b11
FG
200 if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node {
201 // At least for integers, shifts are already handled by the CTFE
202 return;
203 }
9ffffee4
FG
204 match (
205 Self::literal_integer(cx, actual_lhs),
206 Self::literal_integer(cx, actual_rhs),
207 ) {
487cf647 208 (None, None) => false,
353b0b11
FG
209 (None, Some(n)) => match (&op.node, n) {
210 // Division and module are always valid if applied to non-zero integers
211 (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
212 // Adding or subtracting zeros is always a no-op
213 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
214 // Multiplication by 1 or 0 will never overflow
215 | (hir::BinOpKind::Mul, 0 | 1)
216 => true,
217 _ => false,
218 },
219 (Some(n), None) => match (&op.node, n) {
220 // Adding or subtracting zeros is always a no-op
487cf647 221 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
353b0b11
FG
222 // Multiplication by 1 or 0 will never overflow
223 | (hir::BinOpKind::Mul, 0 | 1)
224 => true,
487cf647
FG
225 _ => false,
226 },
227 (Some(_), Some(_)) => {
228 matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
229 },
2b03887a
FG
230 }
231 } else {
232 false
233 };
234 if !has_valid_op {
235 self.issue_lint(cx, expr);
f2b60f7d 236 }
f2b60f7d 237 }
487cf647 238
49aad941
FG
239 /// There are some integer methods like `wrapping_div` that will panic depending on the
240 /// provided input.
241 fn manage_method_call<'tcx>(
242 &mut self,
ed00b5ec 243 args: &'tcx [hir::Expr<'_>],
49aad941 244 cx: &LateContext<'tcx>,
ed00b5ec
FG
245 ps: &'tcx hir::PathSegment<'_>,
246 receiver: &'tcx hir::Expr<'_>,
49aad941 247 ) {
add651ee
FG
248 let Some(arg) = args.first() else {
249 return;
250 };
49aad941
FG
251 if constant_simple(cx, cx.typeck_results(), receiver).is_some() {
252 return;
253 }
254 let instance_ty = cx.typeck_results().expr_ty(receiver);
255 if !Self::is_integral(instance_ty) {
256 return;
257 }
258 if !self.integer_methods.contains(&ps.ident.name) {
259 return;
260 }
261 let (actual_arg, _) = peel_hir_expr_refs(arg);
262 match Self::literal_integer(cx, actual_arg) {
263 None | Some(0) => self.issue_lint(cx, arg),
264 Some(_) => {},
265 }
266 }
267
487cf647
FG
268 fn manage_unary_ops<'tcx>(
269 &mut self,
270 cx: &LateContext<'tcx>,
ed00b5ec
FG
271 expr: &'tcx hir::Expr<'_>,
272 un_expr: &'tcx hir::Expr<'_>,
487cf647
FG
273 un_op: hir::UnOp,
274 ) {
add651ee
FG
275 let hir::UnOp::Neg = un_op else {
276 return;
277 };
487cf647
FG
278 if constant(cx, cx.typeck_results(), un_expr).is_some() {
279 return;
280 }
281 let ty = cx.typeck_results().expr_ty(expr).peel_refs();
9c376795 282 if self.has_allowed_unary(ty) {
487cf647
FG
283 return;
284 }
285 let actual_un_expr = peel_hir_expr_refs(un_expr).0;
9ffffee4 286 if Self::literal_integer(cx, actual_un_expr).is_some() {
487cf647
FG
287 return;
288 }
289 self.issue_lint(cx, expr);
290 }
291
49aad941 292 fn should_skip_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
9ffffee4
FG
293 is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id)
294 || self.expr_span.is_some()
295 || self.const_span.map_or(false, |sp| sp.contains(expr.span))
487cf647 296 }
f2b60f7d
FG
297}
298
299impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
ed00b5ec 300 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
9ffffee4 301 if self.should_skip_expr(cx, expr) {
f2b60f7d
FG
302 return;
303 }
304 match &expr.kind {
487cf647 305 hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
f2b60f7d
FG
306 self.manage_bin_ops(cx, expr, op, lhs, rhs);
307 },
49aad941
FG
308 hir::ExprKind::MethodCall(ps, receiver, args, _) => {
309 self.manage_method_call(args, cx, ps, receiver);
310 },
487cf647
FG
311 hir::ExprKind::Unary(un_op, un_expr) => {
312 self.manage_unary_ops(cx, expr, un_expr, *un_op);
f2b60f7d
FG
313 },
314 _ => {},
315 }
316 }
317
318 fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
319 let body_owner = cx.tcx.hir().body_owner(body.id());
9ffffee4
FG
320 let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id());
321
f2b60f7d 322 let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
781aab86 323 if let hir::BodyOwnerKind::Const { .. } | hir::BodyOwnerKind::Static(_) = body_owner_kind {
f2b60f7d 324 let body_span = cx.tcx.hir().span_with_body(body_owner);
ed00b5ec
FG
325 if let Some(span) = self.const_span
326 && span.contains(body_span)
327 {
f2b60f7d
FG
328 return;
329 }
330 self.const_span = Some(body_span);
331 }
332 }
333
334 fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
335 let body_owner = cx.tcx.hir().body_owner(body.id());
336 let body_span = cx.tcx.hir().span(body_owner);
ed00b5ec
FG
337 if let Some(span) = self.const_span
338 && span.contains(body_span)
339 {
f2b60f7d
FG
340 return;
341 }
342 self.const_span = None;
343 }
344
345 fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
346 if Some(expr.span) == self.expr_span {
347 self.expr_span = None;
348 }
349 }
350}