55
66use super :: ARITHMETIC ;
77use clippy_utils:: { consts:: constant_simple, diagnostics:: span_lint} ;
8+ use rustc_ast as ast;
89use rustc_data_structures:: fx:: FxHashSet ;
910use rustc_hir as hir;
1011use rustc_lint:: { LateContext , LateLintPass } ;
12+ use rustc_middle:: ty:: Ty ;
1113use rustc_session:: impl_lint_pass;
12- use rustc_span:: source_map:: Span ;
14+ use rustc_span:: source_map:: { Span , Spanned } ;
1315
14- const HARD_CODED_ALLOWED : & [ & str ] = & [ "std::num::Saturating" , "std::string::String" , "std::num::Wrapping" ] ;
16+ const HARD_CODED_ALLOWED : & [ & str ] = & [
17+ "f32" ,
18+ "f64" ,
19+ "std::num::Saturating" ,
20+ "std::string::String" ,
21+ "std::num::Wrapping" ,
22+ ] ;
1523
1624#[ derive( Debug ) ]
1725pub struct Arithmetic {
@@ -34,6 +42,27 @@ impl Arithmetic {
3442 }
3543 }
3644
45+ /// Checks assign operators (+=, -=, *=, /=) of integers in a non-constant environment that
46+ /// won't overflow.
47+ fn has_valid_assign_op ( op : & Spanned < hir:: BinOpKind > , rhs : & hir:: Expr < ' _ > , rhs_refs : Ty < ' _ > ) -> bool {
48+ if !Self :: is_literal_integer ( rhs, rhs_refs) {
49+ return false ;
50+ }
51+ if let hir:: BinOpKind :: Div | hir:: BinOpKind :: Mul = op. node
52+ && let hir:: ExprKind :: Lit ( ref lit) = rhs. kind
53+ && let ast:: LitKind :: Int ( 1 , _) = lit. node
54+ {
55+ return true ;
56+ }
57+ false
58+ }
59+
60+ /// Checks "raw" binary operators (+, -, *, /) of integers in a non-constant environment
61+ /// already handled by the CTFE.
62+ fn has_valid_bin_op ( lhs : & hir:: Expr < ' _ > , lhs_refs : Ty < ' _ > , rhs : & hir:: Expr < ' _ > , rhs_refs : Ty < ' _ > ) -> bool {
63+ Self :: is_literal_integer ( lhs, lhs_refs) && Self :: is_literal_integer ( rhs, rhs_refs)
64+ }
65+
3766 /// Checks if the given `expr` has any of the inner `allowed` elements.
3867 fn is_allowed_ty ( & self , cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
3968 self . allowed . contains (
@@ -46,40 +75,66 @@ impl Arithmetic {
4675 )
4776 }
4877
78+ /// Explicit integers like `1` or `i32::MAX`. Does not take into consideration references.
79+ fn is_literal_integer ( expr : & hir:: Expr < ' _ > , expr_refs : Ty < ' _ > ) -> bool {
80+ let is_integral = expr_refs. is_integral ( ) ;
81+ let is_literal = matches ! ( expr. kind, hir:: ExprKind :: Lit ( _) ) ;
82+ is_integral && is_literal
83+ }
84+
4985 fn issue_lint ( & mut self , cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) {
5086 span_lint ( cx, ARITHMETIC , expr. span , "arithmetic detected" ) ;
5187 self . expr_span = Some ( expr. span ) ;
5288 }
89+
90+ /// Manages when the lint should be triggered. Operations in constant environments, hard coded
91+ /// types, custom allowed types and non-constant operations that won't overflow are ignored.
92+ fn manage_bin_ops (
93+ & mut self ,
94+ cx : & LateContext < ' _ > ,
95+ expr : & hir:: Expr < ' _ > ,
96+ op : & Spanned < hir:: BinOpKind > ,
97+ lhs : & hir:: Expr < ' _ > ,
98+ rhs : & hir:: Expr < ' _ > ,
99+ ) {
100+ if constant_simple ( cx, cx. typeck_results ( ) , expr) . is_some ( ) {
101+ return ;
102+ }
103+ if !matches ! (
104+ op. node,
105+ hir:: BinOpKind :: Add
106+ | hir:: BinOpKind :: Sub
107+ | hir:: BinOpKind :: Mul
108+ | hir:: BinOpKind :: Div
109+ | hir:: BinOpKind :: Rem
110+ | hir:: BinOpKind :: Shl
111+ | hir:: BinOpKind :: Shr
112+ ) {
113+ return ;
114+ } ;
115+ if self . is_allowed_ty ( cx, lhs) || self . is_allowed_ty ( cx, rhs) {
116+ return ;
117+ }
118+ let lhs_refs = cx. typeck_results ( ) . expr_ty ( lhs) . peel_refs ( ) ;
119+ let rhs_refs = cx. typeck_results ( ) . expr_ty ( rhs) . peel_refs ( ) ;
120+ let has_valid_assign_op = Self :: has_valid_assign_op ( op, rhs, rhs_refs) ;
121+ if has_valid_assign_op || Self :: has_valid_bin_op ( lhs, lhs_refs, rhs, rhs_refs) {
122+ return ;
123+ }
124+ self . issue_lint ( cx, expr) ;
125+ }
53126}
54127
55128impl < ' tcx > LateLintPass < ' tcx > for Arithmetic {
56129 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
57- if self . expr_span . is_some ( ) {
58- return ;
59- }
60- if let Some ( span) = self . const_span && span. contains ( expr. span ) {
130+ if self . expr_span . is_some ( ) || self . const_span . map_or ( false , |sp| sp. contains ( expr. span ) ) {
61131 return ;
62132 }
63133 match & expr. kind {
64134 hir:: ExprKind :: Binary ( op, lhs, rhs) | hir:: ExprKind :: AssignOp ( op, lhs, rhs) => {
65- let (
66- hir:: BinOpKind :: Add
67- | hir:: BinOpKind :: Sub
68- | hir:: BinOpKind :: Mul
69- | hir:: BinOpKind :: Div
70- | hir:: BinOpKind :: Rem
71- | hir:: BinOpKind :: Shl
72- | hir:: BinOpKind :: Shr
73- ) = op. node else {
74- return ;
75- } ;
76- if self . is_allowed_ty ( cx, lhs) || self . is_allowed_ty ( cx, rhs) {
77- return ;
78- }
79- self . issue_lint ( cx, expr) ;
135+ self . manage_bin_ops ( cx, expr, op, lhs, rhs) ;
80136 } ,
81137 hir:: ExprKind :: Unary ( hir:: UnOp :: Neg , _) => {
82- // CTFE already takes care of things like `-1` that do not overflow.
83138 if constant_simple ( cx, cx. typeck_results ( ) , expr) . is_none ( ) {
84139 self . issue_lint ( cx, expr) ;
85140 }
@@ -89,16 +144,15 @@ impl<'tcx> LateLintPass<'tcx> for Arithmetic {
89144 }
90145
91146 fn check_body ( & mut self , cx : & LateContext < ' _ > , body : & hir:: Body < ' _ > ) {
92- let body_owner = cx. tcx . hir ( ) . body_owner_def_id ( body. id ( ) ) ;
93- match cx. tcx . hir ( ) . body_owner_kind ( body_owner) {
94- hir:: BodyOwnerKind :: Const | hir:: BodyOwnerKind :: Static ( _) => {
95- let body_span = cx. tcx . def_span ( body_owner) ;
96- if let Some ( span) = self . const_span && span. contains ( body_span) {
97- return ;
98- }
99- self . const_span = Some ( body_span) ;
100- } ,
101- hir:: BodyOwnerKind :: Closure | hir:: BodyOwnerKind :: Fn => { } ,
147+ let body_owner = cx. tcx . hir ( ) . body_owner ( body. id ( ) ) ;
148+ let body_owner_def_id = cx. tcx . hir ( ) . local_def_id ( body_owner) ;
149+ let body_owner_kind = cx. tcx . hir ( ) . body_owner_kind ( body_owner_def_id) ;
150+ if let hir:: BodyOwnerKind :: Const | hir:: BodyOwnerKind :: Static ( _) = body_owner_kind {
151+ let body_span = cx. tcx . hir ( ) . span_with_body ( body_owner) ;
152+ if let Some ( span) = self . const_span && span. contains ( body_span) {
153+ return ;
154+ }
155+ self . const_span = Some ( body_span) ;
102156 }
103157 }
104158
0 commit comments