1 use super::ARITHMETIC_SIDE_EFFECTS;
3 consts::{constant, constant_simple},
4 diagnostics::span_lint,
8 use rustc_data_structures::fx::FxHashSet;
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_middle::ty::Ty;
12 use rustc_session::impl_lint_pass;
13 use rustc_span::source_map::{Span, Spanned};
15 const HARD_CODED_ALLOWED: &[&str] = &[
19 "std::num::Saturating",
21 "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 /// Checks if the given `expr` has any of the inner `allowed` elements.
46 fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
48 .contains(ty.to_string().split('<').next().unwrap_or_default())
51 // For example, 8i32 or &i64::MAX.
52 fn is_integral(ty: Ty<'_>) -> bool {
53 ty.peel_refs().is_integral()
56 // Common entry-point to avoid code duplication.
57 fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
58 let msg = "arithmetic operation that can potentially result in unexpected side-effects";
59 span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg);
60 self.expr_span = Some(expr.span);
63 /// If `expr` is not a literal integer like `1`, returns `None`.
64 fn literal_integer(expr: &hir::Expr<'_>) -> Option<u128> {
65 if let hir::ExprKind::Lit(ref lit) = expr.kind && let ast::LitKind::Int(n, _) = lit.node {
73 /// Manages when the lint should be triggered. Operations in constant environments, hard coded
74 /// types, custom allowed types and non-constant operations that won't overflow are ignored.
75 fn manage_bin_ops<'tcx>(
77 cx: &LateContext<'tcx>,
78 expr: &hir::Expr<'tcx>,
79 op: &Spanned<hir::BinOpKind>,
80 lhs: &hir::Expr<'tcx>,
81 rhs: &hir::Expr<'tcx>,
83 if constant_simple(cx, cx.typeck_results(), expr).is_some() {
98 let lhs_ty = cx.typeck_results().expr_ty(lhs);
99 let rhs_ty = cx.typeck_results().expr_ty(rhs);
100 let lhs_and_rhs_have_the_same_ty = lhs_ty == rhs_ty;
101 if lhs_and_rhs_have_the_same_ty && self.is_allowed_ty(lhs_ty) && self.is_allowed_ty(rhs_ty) {
104 let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
105 let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
106 let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
107 match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) {
108 (None, None) => false,
109 (None, Some(n)) | (Some(n), None) => match (&op.node, n) {
110 (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
111 (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
112 | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
113 | (hir::BinOpKind::Mul, 0 | 1) => true,
116 (Some(_), Some(_)) => {
117 matches!((lhs_ref_counter, rhs_ref_counter), (0, 0))
124 self.issue_lint(cx, expr);
128 fn manage_unary_ops<'tcx>(
130 cx: &LateContext<'tcx>,
131 expr: &hir::Expr<'tcx>,
132 un_expr: &hir::Expr<'tcx>,
135 let hir::UnOp::Neg = un_op else { return; };
136 if constant(cx, cx.typeck_results(), un_expr).is_some() {
139 let ty = cx.typeck_results().expr_ty(expr).peel_refs();
140 if self.is_allowed_ty(ty) {
143 let actual_un_expr = peel_hir_expr_refs(un_expr).0;
144 if Self::literal_integer(actual_un_expr).is_some() {
147 self.issue_lint(cx, expr);
150 fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool {
151 self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span))
155 impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
156 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
157 if self.should_skip_expr(expr) {
161 hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
162 self.manage_bin_ops(cx, expr, op, lhs, rhs);
164 hir::ExprKind::Unary(un_op, un_expr) => {
165 self.manage_unary_ops(cx, expr, un_expr, *un_op);
171 fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
172 let body_owner = cx.tcx.hir().body_owner(body.id());
173 let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
174 let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
175 if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
176 let body_span = cx.tcx.hir().span_with_body(body_owner);
177 if let Some(span) = self.const_span && span.contains(body_span) {
180 self.const_span = Some(body_span);
184 fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
185 let body_owner = cx.tcx.hir().body_owner(body.id());
186 let body_span = cx.tcx.hir().span(body_owner);
187 if let Some(span) = self.const_span && span.contains(body_span) {
190 self.const_span = None;
193 fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
194 if Some(expr.span) == self.expr_span {
195 self.expr_span = None;