]>
Commit | Line | Data |
---|---|---|
f2b60f7d | 1 | use super::ARITHMETIC_SIDE_EFFECTS; |
add651ee FG |
2 | use clippy_utils::consts::{constant, constant_simple, Constant}; |
3 | use clippy_utils::diagnostics::span_lint; | |
781aab86 | 4 | use clippy_utils::ty::type_diagnostic_name; |
add651ee | 5 | use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; |
9c376795 | 6 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
f2b60f7d FG |
7 | use rustc_lint::{LateContext, LateLintPass}; |
8 | use rustc_middle::ty::Ty; | |
9 | use rustc_session::impl_lint_pass; | |
ed00b5ec FG |
10 | use rustc_span::{Span, Symbol}; |
11 | use rustc_span::source_map::Spanned; | |
781aab86 | 12 | use rustc_span::symbol::sym; |
add651ee | 13 | use {rustc_ast as ast, rustc_hir as hir}; |
f2b60f7d | 14 | |
781aab86 | 15 | const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]]; |
9c376795 | 16 | const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"]; |
781aab86 FG |
17 | const 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)] | |
25 | pub 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 | ||
34 | impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]); | |
35 | ||
36 | impl 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 | ||
299 | impl<'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 | } |