]>
Commit | Line | Data |
---|---|---|
17df50a5 | 1 | use clippy_utils::consts::{constant, Constant}; |
cdc7bbd5 XL |
2 | use clippy_utils::diagnostics::span_lint_and_then; |
3 | use clippy_utils::sext; | |
f20569fa XL |
4 | use if_chain::if_chain; |
5 | use rustc_hir::{BinOpKind, Expr, ExprKind}; | |
6 | use rustc_lint::{LateContext, LateLintPass}; | |
cdc7bbd5 | 7 | use rustc_middle::ty; |
f20569fa XL |
8 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
9 | use std::fmt::Display; | |
10 | ||
11 | declare_clippy_lint! { | |
94222f64 XL |
12 | /// ### What it does |
13 | /// Checks for modulo arithmetic. | |
f20569fa | 14 | /// |
94222f64 XL |
15 | /// ### Why is this bad? |
16 | /// The results of modulo (%) operation might differ | |
f20569fa XL |
17 | /// depending on the language, when negative numbers are involved. |
18 | /// If you interop with different languages it might be beneficial | |
19 | /// to double check all places that use modulo arithmetic. | |
20 | /// | |
21 | /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`. | |
22 | /// | |
94222f64 | 23 | /// ### Example |
f20569fa XL |
24 | /// ```rust |
25 | /// let x = -17 % 3; | |
26 | /// ``` | |
a2a8927a | 27 | #[clippy::version = "1.42.0"] |
f20569fa XL |
28 | pub MODULO_ARITHMETIC, |
29 | restriction, | |
30 | "any modulo arithmetic statement" | |
31 | } | |
32 | ||
33 | declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]); | |
34 | ||
35 | struct OperandInfo { | |
36 | string_representation: Option<String>, | |
37 | is_negative: bool, | |
38 | is_integral: bool, | |
39 | } | |
40 | ||
41 | fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OperandInfo> { | |
42 | match constant(cx, cx.typeck_results(), operand) { | |
43 | Some((Constant::Int(v), _)) => match *cx.typeck_results().expr_ty(expr).kind() { | |
44 | ty::Int(ity) => { | |
45 | let value = sext(cx.tcx, v, ity); | |
46 | return Some(OperandInfo { | |
47 | string_representation: Some(value.to_string()), | |
48 | is_negative: value < 0, | |
49 | is_integral: true, | |
50 | }); | |
51 | }, | |
52 | ty::Uint(_) => { | |
53 | return Some(OperandInfo { | |
54 | string_representation: None, | |
55 | is_negative: false, | |
56 | is_integral: true, | |
57 | }); | |
58 | }, | |
59 | _ => {}, | |
60 | }, | |
61 | Some((Constant::F32(f), _)) => { | |
62 | return Some(floating_point_operand_info(&f)); | |
63 | }, | |
64 | Some((Constant::F64(f), _)) => { | |
65 | return Some(floating_point_operand_info(&f)); | |
66 | }, | |
67 | _ => {}, | |
68 | } | |
69 | None | |
70 | } | |
71 | ||
72 | fn floating_point_operand_info<T: Display + PartialOrd + From<f32>>(f: &T) -> OperandInfo { | |
73 | OperandInfo { | |
74 | string_representation: Some(format!("{:.3}", *f)), | |
75 | is_negative: *f < 0.0.into(), | |
76 | is_integral: false, | |
77 | } | |
78 | } | |
79 | ||
80 | fn might_have_negative_value(t: &ty::TyS<'_>) -> bool { | |
81 | t.is_signed() || t.is_floating_point() | |
82 | } | |
83 | ||
84 | fn check_const_operands<'tcx>( | |
85 | cx: &LateContext<'tcx>, | |
86 | expr: &'tcx Expr<'_>, | |
87 | lhs_operand: &OperandInfo, | |
88 | rhs_operand: &OperandInfo, | |
89 | ) { | |
90 | if lhs_operand.is_negative ^ rhs_operand.is_negative { | |
91 | span_lint_and_then( | |
92 | cx, | |
93 | MODULO_ARITHMETIC, | |
94 | expr.span, | |
95 | &format!( | |
96 | "you are using modulo operator on constants with different signs: `{} % {}`", | |
97 | lhs_operand.string_representation.as_ref().unwrap(), | |
98 | rhs_operand.string_representation.as_ref().unwrap() | |
99 | ), | |
100 | |diag| { | |
101 | diag.note("double check for expected result especially when interoperating with different languages"); | |
102 | if lhs_operand.is_integral { | |
103 | diag.note("or consider using `rem_euclid` or similar function"); | |
104 | } | |
105 | }, | |
106 | ); | |
107 | } | |
108 | } | |
109 | ||
110 | fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, operand: &Expr<'_>) { | |
111 | let operand_type = cx.typeck_results().expr_ty(operand); | |
112 | if might_have_negative_value(operand_type) { | |
113 | span_lint_and_then( | |
114 | cx, | |
115 | MODULO_ARITHMETIC, | |
116 | expr.span, | |
117 | "you are using modulo operator on types that might have different signs", | |
118 | |diag| { | |
119 | diag.note("double check for expected result especially when interoperating with different languages"); | |
120 | if operand_type.is_integral() { | |
121 | diag.note("or consider using `rem_euclid` or similar function"); | |
122 | } | |
123 | }, | |
124 | ); | |
125 | } | |
126 | } | |
127 | ||
128 | impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic { | |
129 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
130 | match &expr.kind { | |
131 | ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => { | |
c295e0f8 | 132 | if op.node == BinOpKind::Rem { |
f20569fa XL |
133 | let lhs_operand = analyze_operand(lhs, cx, expr); |
134 | let rhs_operand = analyze_operand(rhs, cx, expr); | |
135 | if_chain! { | |
136 | if let Some(lhs_operand) = lhs_operand; | |
137 | if let Some(rhs_operand) = rhs_operand; | |
138 | then { | |
139 | check_const_operands(cx, expr, &lhs_operand, &rhs_operand); | |
140 | } | |
141 | else { | |
142 | check_non_const_operands(cx, expr, lhs); | |
143 | } | |
144 | } | |
145 | }; | |
146 | }, | |
147 | _ => {}, | |
148 | } | |
149 | } | |
150 | } |