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