11use super :: ARITHMETIC_SIDE_EFFECTS ;
2+ use crate :: clippy_utils:: res:: MaybeQPath ;
23use clippy_config:: Conf ;
34use clippy_utils:: consts:: { ConstEvalCtxt , Constant } ;
45use clippy_utils:: diagnostics:: span_lint;
56use clippy_utils:: res:: MaybeDef ;
67use clippy_utils:: { expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym} ;
78use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
89use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_middle:: ty:: { self , Ty } ;
10+ use rustc_middle:: ty:: { self , Ty , UintTy } ;
1011use rustc_session:: impl_lint_pass;
1112use rustc_span:: { Span , Symbol } ;
1213use { rustc_ast as ast, rustc_hir as hir} ;
@@ -88,74 +89,16 @@ impl ArithmeticSideEffects {
8889 self . allowed_unary . contains ( ty_string_elem)
8990 }
9091
91- fn is_non_zero_u ( cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
92- if let ty:: Adt ( adt, substs) = ty. kind ( )
93- && cx. tcx . is_diagnostic_item ( sym:: NonZero , adt. did ( ) )
94- && let int_type = substs. type_at ( 0 )
95- && matches ! ( int_type. kind( ) , ty:: Uint ( _) )
96- {
97- true
98- } else {
99- false
100- }
101- }
102-
103- /// Verifies built-in types that have specific allowed operations
104- fn has_specific_allowed_type_and_operation < ' tcx > (
105- cx : & LateContext < ' tcx > ,
106- lhs_ty : Ty < ' tcx > ,
107- op : hir:: BinOpKind ,
108- rhs_ty : Ty < ' tcx > ,
109- ) -> bool {
110- let is_div_or_rem = matches ! ( op, hir:: BinOpKind :: Div | hir:: BinOpKind :: Rem ) ;
111- let is_sat_or_wrap = |ty : Ty < ' _ > | ty. is_diag_item ( cx, sym:: Saturating ) || ty. is_diag_item ( cx, sym:: Wrapping ) ;
112-
113- // If the RHS is `NonZero<u*>`, then division or module by zero will never occur.
114- if Self :: is_non_zero_u ( cx, rhs_ty) && is_div_or_rem {
115- return true ;
116- }
117-
118- // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module.
119- if is_sat_or_wrap ( lhs_ty) {
120- return !is_div_or_rem;
121- }
122-
123- false
124- }
125-
126- // For example, 8i32 or &i64::MAX.
127- fn is_integral ( ty : Ty < ' _ > ) -> bool {
128- ty. peel_refs ( ) . is_integral ( )
129- }
130-
13192 // Common entry-point to avoid code duplication.
13293 fn issue_lint < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
13394 if is_from_proc_macro ( cx, expr) {
13495 return ;
13596 }
136-
13797 let msg = "arithmetic operation that can potentially result in unexpected side-effects" ;
13898 span_lint ( cx, ARITHMETIC_SIDE_EFFECTS , expr. span , msg) ;
13999 self . expr_span = Some ( expr. span ) ;
140100 }
141101
142- /// Returns the numeric value of a literal integer originated from `expr`, if any.
143- ///
144- /// Literal integers can be originated from adhoc declarations like `1`, associated constants
145- /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`,
146- fn literal_integer ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> Option < u128 > {
147- let actual = peel_hir_expr_unary ( expr) . 0 ;
148- if let hir:: ExprKind :: Lit ( lit) = actual. kind
149- && let ast:: LitKind :: Int ( n, _) = lit. node
150- {
151- return Some ( n. get ( ) ) ;
152- }
153- if let Some ( Constant :: Int ( n) ) = ConstEvalCtxt :: new ( cx) . eval ( expr) {
154- return Some ( n) ;
155- }
156- None
157- }
158-
159102 /// Methods like `add_assign` are send to their `BinOps` references.
160103 fn manage_sugar_methods < ' tcx > (
161104 & mut self ,
@@ -213,59 +156,53 @@ impl ArithmeticSideEffects {
213156 && let hir:: ExprKind :: MethodCall ( method, receiver, [ ] , _) = actual_lhs. kind
214157 && method. ident . name == sym:: get
215158 && let receiver_ty = cx. typeck_results ( ) . expr_ty ( receiver) . peel_refs ( )
216- && Self :: is_non_zero_u ( cx, receiver_ty)
217- && let Some ( 1 ) = Self :: literal_integer ( cx, actual_rhs)
159+ && is_non_zero_u ( cx, receiver_ty)
160+ && literal_integer ( cx, actual_rhs) == Some ( 1 )
218161 {
219162 return ;
220163 }
221164
222165 let lhs_ty = cx. typeck_results ( ) . expr_ty ( actual_lhs) . peel_refs ( ) ;
223166 let rhs_ty = cx. typeck_results ( ) . expr_ty_adjusted ( actual_rhs) . peel_refs ( ) ;
224- if self . has_allowed_binary ( lhs_ty, rhs_ty) {
225- return ;
226- }
227- if Self :: has_specific_allowed_type_and_operation ( cx, lhs_ty, op, rhs_ty) {
167+ if self . has_allowed_binary ( lhs_ty, rhs_ty)
168+ | has_specific_allowed_type_and_operation ( cx, lhs_ty, op, rhs_ty)
169+ | is_safe_due_to_smaller_source_type ( cx, op, ( actual_lhs, lhs_ty) , actual_rhs)
170+ | is_safe_due_to_smaller_source_type ( cx, op, ( actual_rhs, rhs_ty) , actual_lhs)
171+ {
228172 return ;
229173 }
230-
231- let has_valid_op = if Self :: is_integral ( lhs_ty) && Self :: is_integral ( rhs_ty) {
174+ if is_integer ( lhs_ty) && is_integer ( rhs_ty) {
232175 if let hir:: BinOpKind :: Shl | hir:: BinOpKind :: Shr = op {
233176 // At least for integers, shifts are already handled by the CTFE
234177 return ;
235178 }
236- match (
237- Self :: literal_integer ( cx, actual_lhs) ,
238- Self :: literal_integer ( cx, actual_rhs) ,
239- ) {
240- ( None , None ) => false ,
179+ match ( literal_integer ( cx, actual_lhs) , literal_integer ( cx, actual_rhs) ) {
241180 ( None , Some ( n) ) => match ( & op, n) {
242181 // Division and module are always valid if applied to non-zero integers
243- ( hir:: BinOpKind :: Div | hir:: BinOpKind :: Rem , local_n) if local_n != 0 => true ,
244- // Adding or subtracting zeros is always a no-op
245- ( hir:: BinOpKind :: Add | hir:: BinOpKind :: Sub , 0 )
246- // Multiplication by 1 or 0 will never overflow
247- | ( hir:: BinOpKind :: Mul , 0 | 1 )
248- => true ,
249- _ => false ,
250- } ,
251- ( Some ( n) , None ) => match ( & op, n) {
182+ ( hir:: BinOpKind :: Div | hir:: BinOpKind :: Rem , local_n) if local_n != 0 => return ,
252183 // Adding or subtracting zeros is always a no-op
253184 ( hir:: BinOpKind :: Add | hir:: BinOpKind :: Sub , 0 )
254185 // Multiplication by 1 or 0 will never overflow
255186 | ( hir:: BinOpKind :: Mul , 0 | 1 )
256- => true ,
257- _ => false ,
187+ => return ,
188+ _ => { } ,
258189 } ,
259- ( Some ( _) , Some ( _) ) => {
260- matches ! ( ( lhs_ref_counter, rhs_ref_counter) , ( 0 , 0 ) )
190+ ( Some ( n) , None )
191+ if matches ! (
192+ ( & op, n) ,
193+ // Adding or subtracting zeros is always a no-op
194+ ( hir:: BinOpKind :: Add | hir:: BinOpKind :: Sub , 0 )
195+ // Multiplication by 1 or 0 will never overflow
196+ | ( hir:: BinOpKind :: Mul , 0 | 1 )
197+ ) =>
198+ {
199+ return ;
261200 } ,
201+ ( Some ( _) , Some ( _) ) if matches ! ( ( lhs_ref_counter, rhs_ref_counter) , ( 0 , 0 ) ) => return ,
202+ _ => { } ,
262203 }
263- } else {
264- false
265- } ;
266- if !has_valid_op {
267- self . issue_lint ( cx, expr) ;
268204 }
205+ self . issue_lint ( cx, expr) ;
269206 }
270207
271208 /// There are some integer methods like `wrapping_div` that will panic depending on the
@@ -285,15 +222,15 @@ impl ArithmeticSideEffects {
285222 return ;
286223 }
287224 let instance_ty = cx. typeck_results ( ) . expr_ty_adjusted ( receiver) ;
288- if !Self :: is_integral ( instance_ty) {
225+ if !is_integer ( instance_ty) {
289226 return ;
290227 }
291228 self . manage_sugar_methods ( cx, expr, receiver, ps, arg) ;
292229 if !self . disallowed_int_methods . contains ( & ps. ident . name ) {
293230 return ;
294231 }
295232 let ( actual_arg, _) = peel_hir_expr_refs ( arg) ;
296- match Self :: literal_integer ( cx, actual_arg) {
233+ match literal_integer ( cx, actual_arg) {
297234 None | Some ( 0 ) => self . issue_lint ( cx, arg) ,
298235 Some ( _) => { } ,
299236 }
@@ -317,7 +254,7 @@ impl ArithmeticSideEffects {
317254 return ;
318255 }
319256 let actual_un_expr = peel_hir_expr_refs ( un_expr) . 0 ;
320- if Self :: literal_integer ( cx, actual_un_expr) . is_some ( ) {
257+ if literal_integer ( cx, actual_un_expr) . is_some ( ) {
321258 return ;
322259 }
323260 self . issue_lint ( cx, expr) ;
@@ -385,3 +322,120 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
385322 }
386323 }
387324}
325+
326+ /// Detects a type-casting conversion and returns the type of the original expression. For
327+ /// example, `let foo = u64::from(bar)`.
328+ fn find_original_primitive_ty < ' tcx > ( cx : & LateContext < ' tcx > , expr : & hir:: Expr < ' _ > ) -> Option < Ty < ' tcx > > {
329+ if let hir:: ExprKind :: Call ( path, [ arg] ) = & expr. kind
330+ && path. res ( cx) . opt_def_id ( ) . is_diag_item ( & cx. tcx , sym:: from_fn)
331+ {
332+ Some ( cx. typeck_results ( ) . expr_ty ( arg) )
333+ } else {
334+ None
335+ }
336+ }
337+
338+ /// Verifies built-in types that have specific allowed operations
339+ fn has_specific_allowed_type_and_operation < ' tcx > (
340+ cx : & LateContext < ' tcx > ,
341+ lhs_ty : Ty < ' tcx > ,
342+ op : hir:: BinOpKind ,
343+ rhs_ty : Ty < ' tcx > ,
344+ ) -> bool {
345+ let is_div_or_rem = matches ! ( op, hir:: BinOpKind :: Div | hir:: BinOpKind :: Rem ) ;
346+ let is_sat_or_wrap = |ty : Ty < ' _ > | matches ! ( ty. opt_diag_name( cx) , Some ( sym:: Saturating | sym:: Wrapping ) ) ;
347+
348+ // If the RHS is `NonZero<u*>`, then division or module by zero will never occur.
349+ if is_non_zero_u ( cx, rhs_ty) && is_div_or_rem {
350+ return true ;
351+ }
352+
353+ // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module.
354+ if is_sat_or_wrap ( lhs_ty) {
355+ return !is_div_or_rem;
356+ }
357+
358+ false
359+ }
360+
361+ // For example, `i8` or `u128` and possible associated references like `&&u16`.
362+ fn is_integer ( ty : Ty < ' _ > ) -> bool {
363+ ty. peel_refs ( ) . is_integral ( )
364+ }
365+
366+ fn is_non_zero_u ( cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
367+ if let ty:: Adt ( adt, substs) = ty. kind ( )
368+ && cx. tcx . is_diagnostic_item ( sym:: NonZero , adt. did ( ) )
369+ && let int_type = substs. type_at ( 0 )
370+ && matches ! ( int_type. kind( ) , ty:: Uint ( _) )
371+ {
372+ true
373+ } else {
374+ false
375+ }
376+ }
377+
378+ /// If one side is a literal it is possible to evaluate overflows as long as the other side has a
379+ /// smaller type. `0` and `1` suffixes indicate different sides.
380+ ///
381+ /// For example, `1000u64 + u64::from(some_runtime_variable_of_type_u8)`.
382+ fn is_safe_due_to_smaller_source_type (
383+ cx : & LateContext < ' _ > ,
384+ op : hir:: BinOpKind ,
385+ ( expr0, ty0) : ( & hir:: Expr < ' _ > , Ty < ' _ > ) ,
386+ expr1 : & hir:: Expr < ' _ > ,
387+ ) -> bool {
388+ let Some ( num0) = literal_integer ( cx, expr0) else {
389+ return false ;
390+ } ;
391+ let Some ( orig_ty1) = find_original_primitive_ty ( cx, expr1) else {
392+ return false ;
393+ } ;
394+ let Some ( num1) = max_int_num ( orig_ty1) else {
395+ return false ;
396+ } ;
397+ let Some ( rslt) = ( match op {
398+ hir:: BinOpKind :: Add => num0. checked_add ( num1) ,
399+ hir:: BinOpKind :: Mul => num0. checked_mul ( num1) ,
400+ _ => None ,
401+ } ) else {
402+ return false ;
403+ } ;
404+ match ty0. peel_refs ( ) . kind ( ) {
405+ ty:: Uint ( UintTy :: U16 ) => u16:: try_from ( rslt) . is_ok ( ) ,
406+ ty:: Uint ( UintTy :: U32 ) => u32:: try_from ( rslt) . is_ok ( ) ,
407+ ty:: Uint ( UintTy :: U64 ) => u64:: try_from ( rslt) . is_ok ( ) ,
408+ ty:: Uint ( UintTy :: U128 ) => true ,
409+ ty:: Uint ( UintTy :: Usize ) => usize:: try_from ( rslt) . is_ok ( ) ,
410+ _ => false ,
411+ }
412+ }
413+
414+ /// Returns the numeric value of a literal integer originated from `expr`, if any.
415+ ///
416+ /// Literal integers can be originated from adhoc declarations like `1`, associated constants
417+ /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`,
418+ fn literal_integer ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> Option < u128 > {
419+ let actual = peel_hir_expr_unary ( expr) . 0 ;
420+ if let hir:: ExprKind :: Lit ( lit) = actual. kind
421+ && let ast:: LitKind :: Int ( n, _) = lit. node
422+ {
423+ return Some ( n. get ( ) ) ;
424+ }
425+ if let Some ( Constant :: Int ( n) ) = ConstEvalCtxt :: new ( cx) . eval ( expr) {
426+ return Some ( n) ;
427+ }
428+ None
429+ }
430+
431+ fn max_int_num ( ty : Ty < ' _ > ) -> Option < u128 > {
432+ match ty. peel_refs ( ) . kind ( ) {
433+ ty:: Uint ( UintTy :: U8 ) => Some ( u8:: MAX . into ( ) ) ,
434+ ty:: Uint ( UintTy :: U16 ) => Some ( u16:: MAX . into ( ) ) ,
435+ ty:: Uint ( UintTy :: U32 ) => Some ( u32:: MAX . into ( ) ) ,
436+ ty:: Uint ( UintTy :: U64 ) => Some ( u64:: MAX . into ( ) ) ,
437+ ty:: Uint ( UintTy :: U128 ) => Some ( u128:: MAX ) ,
438+ ty:: Uint ( UintTy :: Usize ) => usize:: MAX . try_into ( ) . ok ( ) ,
439+ _ => None ,
440+ }
441+ }
0 commit comments