1 use super::ARITHMETIC_SIDE_EFFECTS;
2 use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
4 use rustc_data_structures::fx::FxHashSet;
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::ty::Ty;
8 use rustc_session::impl_lint_pass;
9 use rustc_span::source_map::{Span, Spanned};
11 const HARD_CODED_ALLOWED: &[&str] = &[
15 "std::num::Saturating",
17 "std::string::String",
21 pub struct ArithmeticSideEffects {
22 allowed: FxHashSet<String>,
23 // Used to check whether expressions are constants, such as in enum discriminants and consts
24 const_span: Option<Span>,
25 expr_span: Option<Span>,
28 impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
30 impl ArithmeticSideEffects {
32 pub fn new(mut allowed: FxHashSet<String>) -> Self {
33 allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
41 /// Assuming that `expr` is a literal integer, checks operators (+=, -=, *, /) in a
42 /// non-constant environment that won't overflow.
43 fn has_valid_op(op: &Spanned<hir::BinOpKind>, expr: &hir::Expr<'_>) -> bool {
44 if let hir::ExprKind::Lit(ref lit) = expr.kind &&
45 let ast::LitKind::Int(value, _) = lit.node
47 match (&op.node, value) {
48 (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
49 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
50 | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
51 | (hir::BinOpKind::Mul, 0 | 1) => true,
59 /// Checks if the given `expr` has any of the inner `allowed` elements.
60 fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
62 .contains(ty.to_string().split('<').next().unwrap_or_default())
65 // For example, 8i32 or &i64::MAX.
66 fn is_integral(ty: Ty<'_>) -> bool {
67 ty.peel_refs().is_integral()
70 // Common entry-point to avoid code duplication.
71 fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
72 let msg = "arithmetic operation that can potentially result in unexpected side-effects";
73 span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
74 self.expr_span = Some(expr.span);
77 /// If `expr` does not match any variant of `LiteralIntegerTy`, returns `None`.
78 fn literal_integer<'expr, 'tcx>(expr: &'expr hir::Expr<'tcx>) -> Option<LiteralIntegerTy<'expr, 'tcx>> {
79 if matches!(expr.kind, hir::ExprKind::Lit(_)) {
80 return Some(LiteralIntegerTy::Value(expr));
82 if let hir::ExprKind::AddrOf(.., inn) = expr.kind && let hir::ExprKind::Lit(_) = inn.kind {
83 return Some(LiteralIntegerTy::Ref(inn));
88 /// Manages when the lint should be triggered. Operations in constant environments, hard coded
89 /// types, custom allowed types and non-constant operations that won't overflow are ignored.
90 fn manage_bin_ops<'tcx>(
92 cx: &LateContext<'tcx>,
93 expr: &hir::Expr<'tcx>,
94 op: &Spanned<hir::BinOpKind>,
95 lhs: &hir::Expr<'tcx>,
96 rhs: &hir::Expr<'tcx>,
98 if constant_simple(cx, cx.typeck_results(), expr).is_some() {
104 | hir::BinOpKind::Sub
105 | hir::BinOpKind::Mul
106 | hir::BinOpKind::Div
107 | hir::BinOpKind::Rem
108 | hir::BinOpKind::Shl
109 | hir::BinOpKind::Shr
113 let lhs_ty = cx.typeck_results().expr_ty(lhs);
114 let rhs_ty = cx.typeck_results().expr_ty(rhs);
115 let lhs_and_rhs_have_the_same_ty = lhs_ty == rhs_ty;
116 if lhs_and_rhs_have_the_same_ty && self.is_allowed_ty(lhs_ty) && self.is_allowed_ty(rhs_ty) {
119 let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
120 match (Self::literal_integer(lhs), Self::literal_integer(rhs)) {
121 (None, Some(lit_int_ty)) | (Some(lit_int_ty), None) => Self::has_valid_op(op, lit_int_ty.into()),
122 (Some(LiteralIntegerTy::Value(_)), Some(LiteralIntegerTy::Value(_))) => true,
123 (None, None) | (Some(_), Some(_)) => false,
129 self.issue_lint(cx, expr);
134 impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
135 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
136 if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
140 hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
141 self.manage_bin_ops(cx, expr, op, lhs, rhs);
143 hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
144 if constant_simple(cx, cx.typeck_results(), expr).is_none() {
145 self.issue_lint(cx, expr);
152 fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
153 let body_owner = cx.tcx.hir().body_owner(body.id());
154 let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
155 let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
156 if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
157 let body_span = cx.tcx.hir().span_with_body(body_owner);
158 if let Some(span) = self.const_span && span.contains(body_span) {
161 self.const_span = Some(body_span);
165 fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
166 let body_owner = cx.tcx.hir().body_owner(body.id());
167 let body_span = cx.tcx.hir().span(body_owner);
168 if let Some(span) = self.const_span && span.contains(body_span) {
171 self.const_span = None;
174 fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
175 if Some(expr.span) == self.expr_span {
176 self.expr_span = None;
181 /// Tells if an expression is a integer declared by value or by reference.
183 /// If `LiteralIntegerTy::Ref`, then the contained value will be `hir::ExprKind::Lit` rather
184 /// than `hirExprKind::Addr`.
185 enum LiteralIntegerTy<'expr, 'tcx> {
186 /// For example, `&199`
187 Ref(&'expr hir::Expr<'tcx>),
188 /// For example, `1` or `i32::MAX`
189 Value(&'expr hir::Expr<'tcx>),
192 impl<'expr, 'tcx> From<LiteralIntegerTy<'expr, 'tcx>> for &'expr hir::Expr<'tcx> {
193 fn from(from: LiteralIntegerTy<'expr, 'tcx>) -> Self {
195 LiteralIntegerTy::Ref(elem) | LiteralIntegerTy::Value(elem) => elem,