]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/bit_mask.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / bit_mask.rs
1 use rustc::hir::*;
2 use rustc::lint::*;
3 use rustc::{declare_lint, lint_array};
4 use if_chain::if_chain;
5 use syntax::ast::LitKind;
6 use syntax::codemap::Span;
7 use crate::utils::{span_lint, span_lint_and_then};
8 use crate::utils::sugg::Sugg;
9 use crate::consts::{constant, Constant};
10
11 /// **What it does:** Checks for incompatible bit masks in comparisons.
12 ///
13 /// The formula for detecting if an expression of the type `_ <bit_op> m
14 /// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
15 /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
16 /// table:
17 ///
18 /// |Comparison  |Bit Op|Example     |is always|Formula               |
19 /// |------------|------|------------|---------|----------------------|
20 /// |`==` or `!=`| `&`  |`x & 2 == 3`|`false`  |`c & m != c`          |
21 /// |`<`  or `>=`| `&`  |`x & 2 < 3` |`true`   |`m < c`               |
22 /// |`>`  or `<=`| `&`  |`x & 1 > 1` |`false`  |`m <= c`              |
23 /// |`==` or `!=`| `|`  |`x | 1 == 0`|`false`  |`c | m != c`          |
24 /// |`<`  or `>=`| `|`  |`x | 1 < 1` |`false`  |`m >= c`              |
25 /// |`<=` or `>` | `|`  |`x | 1 > 0` |`true`   |`m > c`               |
26 ///
27 /// **Why is this bad?** If the bits that the comparison cares about are always
28 /// set to zero or one by the bit mask, the comparison is constant `true` or
29 /// `false` (depending on mask, compared value, and operators).
30 ///
31 /// So the code is actively misleading, and the only reason someone would write
32 /// this intentionally is to win an underhanded Rust contest or create a
33 /// test-case for this lint.
34 ///
35 /// **Known problems:** None.
36 ///
37 /// **Example:**
38 /// ```rust
39 /// if (x & 1 == 2) { … }
40 /// ```
41 declare_clippy_lint! {
42     pub BAD_BIT_MASK,
43     correctness,
44     "expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
45 }
46
47 /// **What it does:** Checks for bit masks in comparisons which can be removed
48 /// without changing the outcome. The basic structure can be seen in the
49 /// following table:
50 ///
51 /// |Comparison| Bit Op  |Example    |equals |
52 /// |----------|---------|-----------|-------|
53 /// |`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`|
54 /// |`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`|
55 ///
56 /// **Why is this bad?** Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
57 /// but still a bit misleading, because the bit mask is ineffective.
58 ///
59 /// **Known problems:** False negatives: This lint will only match instances
60 /// where we have figured out the math (which is for a power-of-two compared
61 /// value). This means things like `x | 1 >= 7` (which would be better written
62 /// as `x >= 6`) will not be reported (but bit masks like this are fairly
63 /// uncommon).
64 ///
65 /// **Example:**
66 /// ```rust
67 /// if (x | 1 > 3) { … }
68 /// ```
69 declare_clippy_lint! {
70     pub INEFFECTIVE_BIT_MASK,
71     correctness,
72     "expressions where a bit mask will be rendered useless by a comparison, e.g. `(x | 1) > 2`"
73 }
74
75 /// **What it does:** Checks for bit masks that can be replaced by a call
76 /// to `trailing_zeros`
77 ///
78 /// **Why is this bad?** `x.trailing_zeros() > 4` is much clearer than `x & 15
79 /// == 0`
80 ///
81 /// **Known problems:** llvm generates better code for `x & 15 == 0` on x86
82 ///
83 /// **Example:**
84 /// ```rust
85 /// x & 0x1111 == 0
86 /// ```
87 declare_clippy_lint! {
88     pub VERBOSE_BIT_MASK,
89     style,
90     "expressions where a bit mask is less readable than the corresponding method call"
91 }
92
93 #[derive(Copy, Clone)]
94 pub struct BitMask {
95     verbose_bit_mask_threshold: u64,
96 }
97
98 impl BitMask {
99     pub fn new(verbose_bit_mask_threshold: u64) -> Self {
100         Self {
101             verbose_bit_mask_threshold,
102         }
103     }
104 }
105
106 impl LintPass for BitMask {
107     fn get_lints(&self) -> LintArray {
108         lint_array!(BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK)
109     }
110 }
111
112 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BitMask {
113     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
114         if let ExprKind::Binary(ref cmp, ref left, ref right) = e.node {
115             if cmp.node.is_comparison() {
116                 if let Some(cmp_opt) = fetch_int_literal(cx, right) {
117                     check_compare(cx, left, cmp.node, cmp_opt, e.span)
118                 } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
119                     check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span)
120                 }
121             }
122         }
123         if_chain! {
124             if let ExprKind::Binary(ref op, ref left, ref right) = e.node;
125             if BinOpKind::Eq == op.node;
126             if let ExprKind::Binary(ref op1, ref left1, ref right1) = left.node;
127             if BinOpKind::BitAnd == op1.node;
128             if let ExprKind::Lit(ref lit) = right1.node;
129             if let LitKind::Int(n, _) = lit.node;
130             if let ExprKind::Lit(ref lit1) = right.node;
131             if let LitKind::Int(0, _) = lit1.node;
132             if n.leading_zeros() == n.count_zeros();
133             if n > u128::from(self.verbose_bit_mask_threshold);
134             then {
135                 span_lint_and_then(cx,
136                                    VERBOSE_BIT_MASK,
137                                    e.span,
138                                    "bit mask could be simplified with a call to `trailing_zeros`",
139                                    |db| {
140                     let sugg = Sugg::hir(cx, left1, "...").maybe_par();
141                     db.span_suggestion(e.span, "try", format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()));
142                 });
143             }
144         }
145     }
146 }
147
148 fn invert_cmp(cmp: BinOpKind) -> BinOpKind {
149     match cmp {
150         BinOpKind::Eq => BinOpKind::Eq,
151         BinOpKind::Ne => BinOpKind::Ne,
152         BinOpKind::Lt => BinOpKind::Gt,
153         BinOpKind::Gt => BinOpKind::Lt,
154         BinOpKind::Le => BinOpKind::Ge,
155         BinOpKind::Ge => BinOpKind::Le,
156         _ => BinOpKind::Or, // Dummy
157     }
158 }
159
160
161 fn check_compare(cx: &LateContext, bit_op: &Expr, cmp_op: BinOpKind, cmp_value: u128, span: Span) {
162     if let ExprKind::Binary(ref op, ref left, ref right) = bit_op.node {
163         if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr {
164             return;
165         }
166         fetch_int_literal(cx, right)
167             .or_else(|| fetch_int_literal(cx, left))
168             .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span))
169     }
170 }
171
172 fn check_bit_mask(cx: &LateContext, bit_op: BinOpKind, cmp_op: BinOpKind, mask_value: u128, cmp_value: u128, span: Span) {
173     match cmp_op {
174         BinOpKind::Eq | BinOpKind::Ne => match bit_op {
175             BinOpKind::BitAnd => if mask_value & cmp_value != cmp_value {
176                 if cmp_value != 0 {
177                     span_lint(
178                         cx,
179                         BAD_BIT_MASK,
180                         span,
181                         &format!(
182                             "incompatible bit mask: `_ & {}` can never be equal to `{}`",
183                             mask_value,
184                             cmp_value
185                         ),
186                     );
187                 }
188             } else if mask_value == 0 {
189                 span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
190             },
191             BinOpKind::BitOr => if mask_value | cmp_value != cmp_value {
192                 span_lint(
193                     cx,
194                     BAD_BIT_MASK,
195                     span,
196                     &format!(
197                         "incompatible bit mask: `_ | {}` can never be equal to `{}`",
198                         mask_value,
199                         cmp_value
200                     ),
201                 );
202             },
203             _ => (),
204         },
205         BinOpKind::Lt | BinOpKind::Ge => match bit_op {
206             BinOpKind::BitAnd => if mask_value < cmp_value {
207                 span_lint(
208                     cx,
209                     BAD_BIT_MASK,
210                     span,
211                     &format!(
212                         "incompatible bit mask: `_ & {}` will always be lower than `{}`",
213                         mask_value,
214                         cmp_value
215                     ),
216                 );
217             } else if mask_value == 0 {
218                 span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
219             },
220             BinOpKind::BitOr => if mask_value >= cmp_value {
221                 span_lint(
222                     cx,
223                     BAD_BIT_MASK,
224                     span,
225                     &format!(
226                         "incompatible bit mask: `_ | {}` will never be lower than `{}`",
227                         mask_value,
228                         cmp_value
229                     ),
230                 );
231             } else {
232                 check_ineffective_lt(cx, span, mask_value, cmp_value, "|");
233             },
234             BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"),
235             _ => (),
236         },
237         BinOpKind::Le | BinOpKind::Gt => match bit_op {
238             BinOpKind::BitAnd => if mask_value <= cmp_value {
239                 span_lint(
240                     cx,
241                     BAD_BIT_MASK,
242                     span,
243                     &format!(
244                         "incompatible bit mask: `_ & {}` will never be higher than `{}`",
245                         mask_value,
246                         cmp_value
247                     ),
248                 );
249             } else if mask_value == 0 {
250                 span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
251             },
252             BinOpKind::BitOr => if mask_value > cmp_value {
253                 span_lint(
254                     cx,
255                     BAD_BIT_MASK,
256                     span,
257                     &format!(
258                         "incompatible bit mask: `_ | {}` will always be higher than `{}`",
259                         mask_value,
260                         cmp_value
261                     ),
262                 );
263             } else {
264                 check_ineffective_gt(cx, span, mask_value, cmp_value, "|");
265             },
266             BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"),
267             _ => (),
268         },
269         _ => (),
270     }
271 }
272
273 fn check_ineffective_lt(cx: &LateContext, span: Span, m: u128, c: u128, op: &str) {
274     if c.is_power_of_two() && m < c {
275         span_lint(
276             cx,
277             INEFFECTIVE_BIT_MASK,
278             span,
279             &format!(
280                 "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
281                 op,
282                 m,
283                 c
284             ),
285         );
286     }
287 }
288
289 fn check_ineffective_gt(cx: &LateContext, span: Span, m: u128, c: u128, op: &str) {
290     if (c + 1).is_power_of_two() && m <= c {
291         span_lint(
292             cx,
293             INEFFECTIVE_BIT_MASK,
294             span,
295             &format!(
296                 "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
297                 op,
298                 m,
299                 c
300             ),
301         );
302     }
303 }
304
305 fn fetch_int_literal(cx: &LateContext, lit: &Expr) -> Option<u128> {
306     match constant(cx, cx.tables, lit)?.0 {
307         Constant::Int(n) => Some(n),
308         _ => None,
309     }
310 }