]> git.lizzy.rs Git - rust.git/blob - src/bit_mask.rs
all: make style of lint messages consistent
[rust.git] / src / bit_mask.rs
1 use rustc::plugin::Registry;
2 use rustc::lint::*;
3 use rustc::middle::const_eval::lookup_const_by_id;
4 use rustc::middle::def::*;
5 use syntax::ast::*;
6 use syntax::ast_util::{is_comparison_binop, binop_to_string};
7 use syntax::ptr::P;
8 use syntax::codemap::Span;
9 use utils::span_lint;
10
11 declare_lint! {
12     pub BAD_BIT_MASK,
13     Deny,
14     "Deny the use of incompatible bit masks in comparisons, e.g. \
15      '(a & 1) == 2'"
16 }
17
18 declare_lint! {
19     pub INEFFECTIVE_BIT_MASK,
20     Warn,
21     "Warn on the use of an ineffective bit mask in comparisons, e.g. \
22      '(a & 1) > 2'"
23 }
24
25 /// Checks for incompatible bit masks in comparisons, e.g. `x & 1 == 2`.
26 /// This cannot work because the bit that makes up the value two was
27 /// zeroed out by the bit-and with 1. So the formula for detecting if an
28 /// expression of the type  `_ <bit_op> m <cmp_op> c` (where `<bit_op>`
29 /// is one of {`&`, '|'} and `<cmp_op>` is one of {`!=`, `>=`, `>` ,
30 /// `!=`, `>=`, `>`}) can be determined from the following table:
31 ///
32 /// |Comparison  |Bit-Op|Example     |is always|Formula               |
33 /// |------------|------|------------|---------|----------------------|
34 /// |`==` or `!=`| `&`  |`x & 2 == 3`|`false`  |`c & m != c`          |
35 /// |`<`  or `>=`| `&`  |`x & 2 < 3` |`true`   |`m < c`               |
36 /// |`>`  or `<=`| `&`  |`x & 1 > 1` |`false`  |`m <= c`              |
37 /// |`==` or `!=`| `|`  |`x | 1 == 0`|`false`  |`c | m != c`          |
38 /// |`<`  or `>=`| `|`  |`x | 1 < 1` |`false`  |`m >= c`              |
39 /// |`<=` or `>` | `|`  |`x | 1 > 0` |`true`   |`m > c`               |
40 ///
41 /// This lint is **deny** by default
42 ///
43 /// There is also a lint that warns on ineffective masks that is *warn*
44 /// by default
45 #[derive(Copy,Clone)]
46 pub struct BitMask;
47
48 impl LintPass for BitMask {
49     fn get_lints(&self) -> LintArray {
50         lint_array!(BAD_BIT_MASK, INEFFECTIVE_BIT_MASK)
51     }
52
53     fn check_expr(&mut self, cx: &Context, e: &Expr) {
54         if let ExprBinary(ref cmp, ref left, ref right) = e.node {
55             if is_comparison_binop(cmp.node) {
56                 fetch_int_literal(cx, right).map_or_else(||
57                     fetch_int_literal(cx, left).map_or((), |cmp_val|
58                         check_compare(cx, right, invert_cmp(cmp.node),
59                                       cmp_val, &e.span)),
60                     |cmp_opt| check_compare(cx, left, cmp.node, cmp_opt,
61                                             &e.span))
62             }
63         }
64     }
65 }
66
67 fn invert_cmp(cmp : BinOp_) -> BinOp_ {
68     match cmp {
69         BiEq => BiEq,
70         BiNe => BiNe,
71         BiLt => BiGt,
72         BiGt => BiLt,
73         BiLe => BiGe,
74         BiGe => BiLe,
75         _ => BiOr // Dummy
76     }
77 }
78
79
80 fn check_compare(cx: &Context, bit_op: &Expr, cmp_op: BinOp_, cmp_value: u64, span: &Span) {
81     match &bit_op.node {
82         &ExprParen(ref subexp) => check_compare(cx, subexp, cmp_op, cmp_value, span),
83         &ExprBinary(ref op, ref left, ref right) => {
84             if op.node != BiBitAnd && op.node != BiBitOr { return; }
85             fetch_int_literal(cx, right).or_else(|| fetch_int_literal(
86                 cx, left)).map_or((), |mask| check_bit_mask(cx, op.node,
87                                                             cmp_op, mask, cmp_value, span))
88         },
89         _ => ()
90     }
91 }
92
93 fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_,
94                   mask_value: u64, cmp_value: u64, span: &Span) {
95     match cmp_op {
96         BiEq | BiNe => match bit_op {
97             BiBitAnd => if mask_value & cmp_value != mask_value {
98                 if cmp_value != 0 {
99                     span_lint(cx, BAD_BIT_MASK, *span, &format!(
100                         "incompatible bit mask: `_ & {}` can never be equal to `{}`",
101                         mask_value, cmp_value));
102                 }
103             } else {
104                 if mask_value == 0 {
105                     span_lint(cx, BAD_BIT_MASK, *span,
106                               &format!("&-masking with zero"));
107                 }
108             },
109             BiBitOr => if mask_value | cmp_value != cmp_value {
110                 span_lint(cx, BAD_BIT_MASK, *span, &format!(
111                     "incompatible bit mask: `_ | {}` can never be equal to `{}`",
112                     mask_value, cmp_value));
113             },
114             _ => ()
115         },
116         BiLt | BiGe => match bit_op {
117             BiBitAnd => if mask_value < cmp_value {
118                 span_lint(cx, BAD_BIT_MASK, *span, &format!(
119                     "incompatible bit mask: `_ & {}` will always be lower than `{}`",
120                     mask_value, cmp_value));
121             } else {
122                 if mask_value == 0 {
123                     span_lint(cx, BAD_BIT_MASK, *span,
124                               &format!("&-masking with zero"));
125                 }
126             },
127             BiBitOr => if mask_value >= cmp_value {
128                 span_lint(cx, BAD_BIT_MASK, *span, &format!(
129                     "incompatible bit mask: `_ | {}` will never be lower than `{}`",
130                     mask_value, cmp_value));
131             } else {
132                 if mask_value < cmp_value {
133                     span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
134                         "ineffective bit mask: `x | {}` compared to `{}` is the same as x compared directly",
135                         mask_value, cmp_value));
136                 }
137             },
138             _ => ()
139         },
140         BiLe | BiGt => match bit_op {
141             BiBitAnd => if mask_value <= cmp_value {
142                 span_lint(cx, BAD_BIT_MASK, *span, &format!(
143                     "incompatible bit mask: `_ & {}` will never be higher than `{}`",
144                     mask_value, cmp_value));
145             } else {
146                 if mask_value == 0 {
147                     span_lint(cx, BAD_BIT_MASK, *span,
148                               &format!("&-masking with zero"));
149                 }
150             },
151             BiBitOr => if mask_value > cmp_value {
152                 span_lint(cx, BAD_BIT_MASK, *span, &format!(
153                     "incompatible bit mask: `_ | {}` will always be higher than `{}`",
154                     mask_value, cmp_value));
155             } else {
156                 if mask_value < cmp_value {
157                     span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
158                         "ineffective bit mask: `x | {}` compared to `{}` is the same as x compared directly",
159                         mask_value, cmp_value));
160                 }
161             },
162             _ => ()
163         },
164         _ => ()
165     }
166 }
167
168 fn fetch_int_literal(cx: &Context, lit : &Expr) -> Option<u64> {
169     match &lit.node {
170         &ExprLit(ref lit_ptr) => {
171             if let &LitInt(value, _) = &lit_ptr.node {
172                 Option::Some(value) //TODO: Handle sign
173             } else { Option::None }
174         },
175         &ExprPath(_, _) => {
176             // Important to let the borrow expire before the const lookup to avoid double
177             // borrowing.
178             let def_map = cx.tcx.def_map.borrow();
179             match def_map.get(&lit.id) {
180                 Some(&PathResolution { base_def: DefConst(def_id), ..}) => Some(def_id),
181                 _ => None
182             }
183         }
184         .and_then(|def_id| lookup_const_by_id(cx.tcx, def_id, Option::None))
185         .and_then(|l| fetch_int_literal(cx, l)),
186         _ => Option::None
187     }
188 }