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