3 clippy::match_same_arms
6 use super::ARITHMETIC_SIDE_EFFECTS;
7 use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
9 use rustc_data_structures::fx::FxHashSet;
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};
16 const HARD_CODED_ALLOWED: &[&str] = &[
19 "std::num::Saturating",
20 "std::string::String",
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>,
32 impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
34 impl ArithmeticSideEffects {
36 pub fn new(mut allowed: FxHashSet<String>) -> Self {
37 allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
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
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, _))
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
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(
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
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);
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.
100 op: &Spanned<hir::BinOpKind>,
104 if constant_simple(cx, cx.typeck_results(), expr).is_some() {
110 | hir::BinOpKind::Sub
111 | hir::BinOpKind::Mul
112 | hir::BinOpKind::Div
113 | hir::BinOpKind::Rem
114 | hir::BinOpKind::Shl
115 | hir::BinOpKind::Shr
119 if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
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()),
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,
132 self.issue_lint(cx, expr);
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)) {
143 hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
144 self.manage_bin_ops(cx, expr, op, lhs, rhs);
146 hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
147 if constant_simple(cx, cx.typeck_results(), expr).is_none() {
148 self.issue_lint(cx, expr);
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) {
164 self.const_span = Some(body_span);
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) {
174 self.const_span = None;
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;