]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
Rollup merge of #103305 - c410-f3r:moar-errors, r=petrochenkov
[rust.git] / src / tools / clippy / clippy_lints / src / operators / arithmetic_side_effects.rs
1 use super::ARITHMETIC_SIDE_EFFECTS;
2 use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
3 use rustc_ast as ast;
4 use rustc_data_structures::fx::FxHashSet;
5 use rustc_hir as hir;
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};
10
11 const HARD_CODED_ALLOWED: &[&str] = &[
12     "&str",
13     "f32",
14     "f64",
15     "std::num::Saturating",
16     "std::num::Wrapping",
17     "std::string::String",
18 ];
19
20 #[derive(Debug)]
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>,
26 }
27
28 impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
29
30 impl ArithmeticSideEffects {
31     #[must_use]
32     pub fn new(mut allowed: FxHashSet<String>) -> Self {
33         allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
34         Self {
35             allowed,
36             const_span: None,
37             expr_span: None,
38         }
39     }
40
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
46         {
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,
52                 _ => false,
53             }
54         } else {
55             false
56         }
57     }
58
59     /// Checks if the given `expr` has any of the inner `allowed` elements.
60     fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
61         self.allowed
62             .contains(ty.to_string().split('<').next().unwrap_or_default())
63     }
64
65     // For example, 8i32 or &i64::MAX.
66     fn is_integral(ty: Ty<'_>) -> bool {
67         ty.peel_refs().is_integral()
68     }
69
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);
75     }
76
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));
81         }
82         if let hir::ExprKind::AddrOf(.., inn) = expr.kind && let hir::ExprKind::Lit(_) = inn.kind {
83             return Some(LiteralIntegerTy::Ref(inn));
84         }
85         None
86     }
87
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>(
91         &mut self,
92         cx: &LateContext<'tcx>,
93         expr: &hir::Expr<'tcx>,
94         op: &Spanned<hir::BinOpKind>,
95         lhs: &hir::Expr<'tcx>,
96         rhs: &hir::Expr<'tcx>,
97     ) {
98         if constant_simple(cx, cx.typeck_results(), expr).is_some() {
99             return;
100         }
101         if !matches!(
102             op.node,
103             hir::BinOpKind::Add
104                 | hir::BinOpKind::Sub
105                 | hir::BinOpKind::Mul
106                 | hir::BinOpKind::Div
107                 | hir::BinOpKind::Rem
108                 | hir::BinOpKind::Shl
109                 | hir::BinOpKind::Shr
110         ) {
111             return;
112         };
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) {
117             return;
118         }
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,
124             }
125         } else {
126             false
127         };
128         if !has_valid_op {
129             self.issue_lint(cx, expr);
130         }
131     }
132 }
133
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)) {
137             return;
138         }
139         match &expr.kind {
140             hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
141                 self.manage_bin_ops(cx, expr, op, lhs, rhs);
142             },
143             hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
144                 if constant_simple(cx, cx.typeck_results(), expr).is_none() {
145                     self.issue_lint(cx, expr);
146                 }
147             },
148             _ => {},
149         }
150     }
151
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) {
159                 return;
160             }
161             self.const_span = Some(body_span);
162         }
163     }
164
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) {
169             return;
170         }
171         self.const_span = None;
172     }
173
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;
177         }
178     }
179 }
180
181 /// Tells if an expression is a integer declared by value or by reference.
182 ///
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>),
190 }
191
192 impl<'expr, 'tcx> From<LiteralIntegerTy<'expr, 'tcx>> for &'expr hir::Expr<'tcx> {
193     fn from(from: LiteralIntegerTy<'expr, 'tcx>) -> Self {
194         match from {
195             LiteralIntegerTy::Ref(elem) | LiteralIntegerTy::Value(elem) => elem,
196         }
197     }
198 }