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