]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/bit_mask.rs
Update to latest master
[rust.git] / clippy_lints / src / bit_mask.rs
1 use rustc::hir::*;
2 use rustc::hir::def::Def;
3 use rustc::lint::*;
4 use rustc_const_eval::lookup_const_by_id;
5 use syntax::ast::LitKind;
6 use syntax::codemap::Span;
7 use utils::span_lint;
8
9 /// **What it does:** Checks for incompatible bit masks in comparisons.
10 ///
11 /// The formula for detecting if an expression of the type `_ <bit_op> m
12 /// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
13 /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
14 /// table:
15 ///
16 /// |Comparison  |Bit Op|Example     |is always|Formula               |
17 /// |------------|------|------------|---------|----------------------|
18 /// |`==` or `!=`| `&`  |`x & 2 == 3`|`false`  |`c & m != c`          |
19 /// |`<`  or `>=`| `&`  |`x & 2 < 3` |`true`   |`m < c`               |
20 /// |`>`  or `<=`| `&`  |`x & 1 > 1` |`false`  |`m <= c`              |
21 /// |`==` or `!=`| `|`  |`x | 1 == 0`|`false`  |`c | m != c`          |
22 /// |`<`  or `>=`| `|`  |`x | 1 < 1` |`false`  |`m >= c`              |
23 /// |`<=` or `>` | `|`  |`x | 1 > 0` |`true`   |`m > c`               |
24 ///
25 /// **Why is this bad?** If the bits that the comparison cares about are always
26 /// set to zero or one by the bit mask, the comparison is constant `true` or
27 /// `false` (depending on mask, compared value, and operators).
28 ///
29 /// So the code is actively misleading, and the only reason someone would write
30 /// this intentionally is to win an underhanded Rust contest or create a
31 /// test-case for this lint.
32 ///
33 /// **Known problems:** None.
34 ///
35 /// **Example:**
36 /// ```rust
37 /// if (x & 1 == 2) { … }
38 /// ```
39 declare_lint! {
40     pub BAD_BIT_MASK,
41     Warn,
42     "expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
43 }
44
45 /// **What it does:** Checks for bit masks in comparisons which can be removed
46 /// without changing the outcome. The basic structure can be seen in the
47 /// following table:
48 ///
49 /// |Comparison| Bit Op  |Example    |equals |
50 /// |----------|---------|-----------|-------|
51 /// |`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`|
52 /// |`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`|
53 ///
54 /// **Why is this bad?** Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
55 /// but still a bit misleading, because the bit mask is ineffective.
56 ///
57 /// **Known problems:** False negatives: This lint will only match instances
58 /// where we have figured out the math (which is for a power-of-two compared
59 /// value). This means things like `x | 1 >= 7` (which would be better written
60 /// as `x >= 6`) will not be reported (but bit masks like this are fairly
61 /// uncommon).
62 ///
63 /// **Example:**
64 /// ```rust
65 /// if (x | 1 > 3) { … }
66 /// ```
67 declare_lint! {
68     pub INEFFECTIVE_BIT_MASK,
69     Warn,
70     "expressions where a bit mask will be rendered useless by a comparison, e.g. `(x | 1) > 2`"
71 }
72
73 #[derive(Copy,Clone)]
74 pub struct BitMask;
75
76 impl LintPass for BitMask {
77     fn get_lints(&self) -> LintArray {
78         lint_array!(BAD_BIT_MASK, INEFFECTIVE_BIT_MASK)
79     }
80 }
81
82 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BitMask {
83     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
84         if let ExprBinary(ref cmp, ref left, ref right) = e.node {
85             if cmp.node.is_comparison() {
86                 if let Some(cmp_opt) = fetch_int_literal(cx, right) {
87                     check_compare(cx, left, cmp.node, cmp_opt, &e.span)
88                 } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
89                     check_compare(cx, right, invert_cmp(cmp.node), cmp_val, &e.span)
90                 }
91             }
92         }
93     }
94 }
95
96 fn invert_cmp(cmp: BinOp_) -> BinOp_ {
97     match cmp {
98         BiEq => BiEq,
99         BiNe => BiNe,
100         BiLt => BiGt,
101         BiGt => BiLt,
102         BiLe => BiGe,
103         BiGe => BiLe,
104         _ => BiOr, // Dummy
105     }
106 }
107
108
109 fn check_compare(cx: &LateContext, bit_op: &Expr, cmp_op: BinOp_, cmp_value: u128, span: &Span) {
110     if let ExprBinary(ref op, ref left, ref right) = bit_op.node {
111         if op.node != BiBitAnd && op.node != BiBitOr {
112             return;
113         }
114         fetch_int_literal(cx, right)
115             .or_else(|| fetch_int_literal(cx, left))
116             .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span))
117     }
118 }
119
120 fn check_bit_mask(cx: &LateContext, bit_op: BinOp_, cmp_op: BinOp_, mask_value: u128, cmp_value: u128, span: &Span) {
121     match cmp_op {
122         BiEq | BiNe => {
123             match bit_op {
124                 BiBitAnd => {
125                     if mask_value & cmp_value != cmp_value {
126                         if cmp_value != 0 {
127                             span_lint(cx,
128                                       BAD_BIT_MASK,
129                                       *span,
130                                       &format!("incompatible bit mask: `_ & {}` can never be equal to `{}`",
131                                                mask_value,
132                                                cmp_value));
133                         }
134                     } else if mask_value == 0 {
135                         span_lint(cx, BAD_BIT_MASK, *span, "&-masking with zero");
136                     }
137                 },
138                 BiBitOr => {
139                     if mask_value | cmp_value != cmp_value {
140                         span_lint(cx,
141                                   BAD_BIT_MASK,
142                                   *span,
143                                   &format!("incompatible bit mask: `_ | {}` can never be equal to `{}`",
144                                            mask_value,
145                                            cmp_value));
146                     }
147                 },
148                 _ => (),
149             }
150         },
151         BiLt | BiGe => {
152             match bit_op {
153                 BiBitAnd => {
154                     if mask_value < cmp_value {
155                         span_lint(cx,
156                                   BAD_BIT_MASK,
157                                   *span,
158                                   &format!("incompatible bit mask: `_ & {}` will always be lower than `{}`",
159                                            mask_value,
160                                            cmp_value));
161                     } else if mask_value == 0 {
162                         span_lint(cx, BAD_BIT_MASK, *span, "&-masking with zero");
163                     }
164                 },
165                 BiBitOr => {
166                     if mask_value >= cmp_value {
167                         span_lint(cx,
168                                   BAD_BIT_MASK,
169                                   *span,
170                                   &format!("incompatible bit mask: `_ | {}` will never be lower than `{}`",
171                                            mask_value,
172                                            cmp_value));
173                     } else {
174                         check_ineffective_lt(cx, *span, mask_value, cmp_value, "|");
175                     }
176                 },
177                 BiBitXor => check_ineffective_lt(cx, *span, mask_value, cmp_value, "^"),
178                 _ => (),
179             }
180         },
181         BiLe | BiGt => {
182             match bit_op {
183                 BiBitAnd => {
184                     if mask_value <= cmp_value {
185                         span_lint(cx,
186                                   BAD_BIT_MASK,
187                                   *span,
188                                   &format!("incompatible bit mask: `_ & {}` will never be higher than `{}`",
189                                            mask_value,
190                                            cmp_value));
191                     } else if mask_value == 0 {
192                         span_lint(cx, BAD_BIT_MASK, *span, "&-masking with zero");
193                     }
194                 },
195                 BiBitOr => {
196                     if mask_value > cmp_value {
197                         span_lint(cx,
198                                   BAD_BIT_MASK,
199                                   *span,
200                                   &format!("incompatible bit mask: `_ | {}` will always be higher than `{}`",
201                                            mask_value,
202                                            cmp_value));
203                     } else {
204                         check_ineffective_gt(cx, *span, mask_value, cmp_value, "|");
205                     }
206                 },
207                 BiBitXor => check_ineffective_gt(cx, *span, mask_value, cmp_value, "^"),
208                 _ => (),
209             }
210         },
211         _ => (),
212     }
213 }
214
215 fn check_ineffective_lt(cx: &LateContext, span: Span, m: u128, c: u128, op: &str) {
216     if c.is_power_of_two() && m < c {
217         span_lint(cx,
218                   INEFFECTIVE_BIT_MASK,
219                   span,
220                   &format!("ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
221                            op,
222                            m,
223                            c));
224     }
225 }
226
227 fn check_ineffective_gt(cx: &LateContext, span: Span, m: u128, c: u128, op: &str) {
228     if (c + 1).is_power_of_two() && m <= c {
229         span_lint(cx,
230                   INEFFECTIVE_BIT_MASK,
231                   span,
232                   &format!("ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
233                            op,
234                            m,
235                            c));
236     }
237 }
238
239 fn fetch_int_literal(cx: &LateContext, lit: &Expr) -> Option<u128> {
240     use rustc::ty::subst::Substs;
241     match lit.node {
242         ExprLit(ref lit_ptr) => {
243             if let LitKind::Int(value, _) = lit_ptr.node {
244                 Some(value) // TODO: Handle sign
245             } else {
246                 None
247             }
248         },
249         ExprPath(ref qpath) => {
250             let def = cx.tables.qpath_def(qpath, lit.id);
251             if let Def::Const(def_id) = def {
252                 lookup_const_by_id(cx.tcx, cx.param_env.and((def_id, Substs::empty()))).and_then(|(l, _ty)| {
253                     let body = if let Some(id) = cx.tcx.hir.as_local_node_id(l) {
254                         cx.tcx.mir_const_qualif(def_id);
255                         cx.tcx.hir.body(cx.tcx.hir.body_owned_by(id))
256                     } else {
257                         cx.tcx.sess.cstore.item_body(cx.tcx, def_id)
258                     };
259                     fetch_int_literal(cx, &body.value)
260                 })
261             } else {
262                 None
263             }
264         },
265         _ => None,
266     }
267 }