Skip to content

Commit 9cb6306

Browse files
committed
New lint: decimal_bitwise_operands
1 parent 92b4b68 commit 9cb6306

File tree

6 files changed

+443
-0
lines changed

6 files changed

+443
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6280,6 +6280,7 @@ Released 2018-09-13
62806280
[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
62816281
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
62826282
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
6283+
[`decimal_bitwise_operands`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_bitwise_operands
62836284
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
62846285
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
62856286
[`default_constructed_unit_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
578578
crate::operators::ASSIGN_OP_PATTERN_INFO,
579579
crate::operators::BAD_BIT_MASK_INFO,
580580
crate::operators::CMP_OWNED_INFO,
581+
crate::operators::DECIMAL_BITWISE_OPERANDS_INFO,
581582
crate::operators::DOUBLE_COMPARISONS_INFO,
582583
crate::operators::DURATION_SUBSEC_INFO,
583584
crate::operators::EQ_OP_INFO,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use clippy_utils::numeric_literal;
3+
use clippy_utils::numeric_literal::NumericLiteral;
4+
use clippy_utils::source::SpanRangeExt;
5+
use rustc_ast::LitKind;
6+
use rustc_data_structures::packed::Pu128;
7+
use rustc_hir::{BinOpKind, Expr, ExprKind};
8+
use rustc_lint::LateContext;
9+
use rustc_span::Span;
10+
11+
use super::DECIMAL_BITWISE_OPERANDS;
12+
13+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, left: &'tcx Expr<'_>, right: &'tcx Expr<'_>) {
14+
if !matches!(op, BinOpKind::BitAnd | BinOpKind::BitOr | BinOpKind::BitXor) {
15+
return;
16+
}
17+
18+
for expr in [left, right] {
19+
check_expr(cx, expr);
20+
}
21+
}
22+
23+
fn check_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
24+
match &expr.kind {
25+
ExprKind::Block(block, _) => {
26+
if let Some(block_expr) = block.expr {
27+
check_expr(cx, block_expr);
28+
}
29+
},
30+
ExprKind::Cast(cast_expr, _) => {
31+
check_expr(cx, cast_expr);
32+
},
33+
ExprKind::Unary(_, unary_expr) => {
34+
check_expr(cx, unary_expr);
35+
},
36+
ExprKind::AddrOf(_, _, addr_of_expr) => {
37+
check_expr(cx, addr_of_expr);
38+
},
39+
ExprKind::Lit(lit) => {
40+
if let LitKind::Int(Pu128(val), _) = lit.node
41+
&& !is_single_digit(val)
42+
&& !is_power_of_twoish(val)
43+
&& let Some(src) = lit.span.get_source_text(cx)
44+
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
45+
&& num_lit.is_decimal()
46+
{
47+
emit_lint(cx, lit.span, num_lit.suffix, val);
48+
}
49+
},
50+
_ => (),
51+
}
52+
}
53+
54+
fn is_power_of_twoish(val: u128) -> bool {
55+
val.is_power_of_two() || val.wrapping_add(1).is_power_of_two()
56+
}
57+
58+
fn is_single_digit(val: u128) -> bool {
59+
val <= 9
60+
}
61+
62+
fn emit_lint(cx: &LateContext<'_>, span: Span, suffix: Option<&str>, val: u128) {
63+
span_lint_and_help(
64+
cx,
65+
DECIMAL_BITWISE_OPERANDS,
66+
span,
67+
"using decimal literal for bitwise operation",
68+
None,
69+
format!(
70+
"use binary ({}), hex ({}), or octal ({}) notation for better readability",
71+
numeric_literal::format(&format!("{val:#b}"), suffix, false),
72+
numeric_literal::format(&format!("{val:#x}"), suffix, false),
73+
numeric_literal::format(&format!("{val:#o}"), suffix, false),
74+
),
75+
);
76+
}

clippy_lints/src/operators/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod assign_op_pattern;
33
mod bit_mask;
44
mod cmp_owned;
55
mod const_comparisons;
6+
mod decimal_bitwise_operands;
67
mod double_comparison;
78
mod duration_subsec;
89
mod eq_op;
@@ -935,6 +936,28 @@ declare_clippy_lint! {
935936
"use of disallowed default division and remainder operations"
936937
}
937938

939+
declare_clippy_lint! {
940+
/// ### What it does
941+
/// Checks for decimal literals used as bit masks in bitwise operations.
942+
///
943+
/// ### Why is this bad?
944+
/// Using decimal literals for bit masks can make the code less readable and obscure the intended bit pattern.
945+
/// Binary, hexadecimal, or octal literals make the bit pattern more explicit and easier to understand at a glance.
946+
///
947+
/// ### Example
948+
/// ```rust,no_run
949+
/// let a = 14 & 6; // Bit pattern is not immediately clear
950+
/// ```
951+
/// Use instead:
952+
/// ```rust,no_run
953+
/// let a = 0b1110 & 0b0110;
954+
/// ```
955+
#[clippy::version = "1.93.0"]
956+
pub DECIMAL_BITWISE_OPERANDS,
957+
pedantic,
958+
"use binary, hex, or octal literals for bitwise operations"
959+
}
960+
938961
pub struct Operators {
939962
arithmetic_context: numeric_arithmetic::Context,
940963
verbose_bit_mask_threshold: u64,
@@ -984,6 +1007,7 @@ impl_lint_pass!(Operators => [
9841007
MANUAL_IS_MULTIPLE_OF,
9851008
MANUAL_DIV_CEIL,
9861009
INVALID_UPCAST_COMPARISONS,
1010+
DECIMAL_BITWISE_OPERANDS
9871011
]);
9881012

9891013
impl<'tcx> LateLintPass<'tcx> for Operators {
@@ -1003,6 +1027,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
10031027
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
10041028
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
10051029
manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv);
1030+
decimal_bitwise_operands::check(cx, op.node, lhs, rhs);
10061031
}
10071032
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
10081033
bit_mask::check(cx, e, op.node, lhs, rhs);
@@ -1028,6 +1053,9 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
10281053
},
10291054
ExprKind::AssignOp(op, lhs, rhs) => {
10301055
let bin_op = op.node.into();
1056+
if !e.span.from_expansion() {
1057+
decimal_bitwise_operands::check(cx, bin_op, lhs, rhs);
1058+
}
10311059
self.arithmetic_context.check_binary(cx, e, bin_op, lhs, rhs);
10321060
misrefactored_assign_op::check(cx, e, bin_op, lhs, rhs);
10331061
modulo_arithmetic::check(cx, e, bin_op, lhs, rhs, false);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#![allow(
2+
clippy::erasing_op,
3+
clippy::no_effect,
4+
clippy::unnecessary_operation,
5+
clippy::unnecessary_cast,
6+
clippy::op_ref
7+
)]
8+
#![warn(clippy::decimal_bitwise_operands)]
9+
10+
macro_rules! bitwise_op {
11+
($x:expr, $y:expr) => {
12+
$x & $y;
13+
};
14+
}
15+
16+
pub const SOME_CONST: i32 = 12345;
17+
18+
fn main() {
19+
let mut x = 0;
20+
// BAD: Bitwise operation, decimal literal, one literal
21+
x & 9_8765_4321; //~ decimal_bitwise_operands
22+
x & 100_i32; //~ decimal_bitwise_operands
23+
x | (/* comment */99); //~ decimal_bitwise_operands
24+
x ^ (99); //~ decimal_bitwise_operands
25+
x &= 99; //~ decimal_bitwise_operands
26+
x |= { 99 }; //~ decimal_bitwise_operands
27+
x |= { { 99 } }; //~ decimal_bitwise_operands
28+
x |= {
29+
0b1000;
30+
99 //~ decimal_bitwise_operands
31+
};
32+
x ^= (99); //~ decimal_bitwise_operands
33+
34+
// BAD: Bitwise operation, decimal literal, two literals
35+
0b1010 & 99; //~ decimal_bitwise_operands
36+
0b1010 | (99); //~ decimal_bitwise_operands
37+
0b1010 ^ (/* comment */99); //~ decimal_bitwise_operands
38+
99 & 0b1010; //~ decimal_bitwise_operands
39+
(99) | 0b1010; //~ decimal_bitwise_operands
40+
(/* comment */99) ^ 0b1010; //~ decimal_bitwise_operands
41+
0xD | { 99 }; //~ decimal_bitwise_operands
42+
88 & 99;
43+
//~^ decimal_bitwise_operands
44+
//~| decimal_bitwise_operands
45+
37 & 38 & 39;
46+
//~^ decimal_bitwise_operands
47+
//~| decimal_bitwise_operands
48+
//~| decimal_bitwise_operands
49+
50+
// GOOD: Bitwise operation, binary/hex/octal literal, one literal
51+
x & 0b1010;
52+
x | 0b1010;
53+
x ^ 0b1010;
54+
x &= 0b1010;
55+
x |= 0b1010;
56+
x ^= 0b1010;
57+
x & 0xD;
58+
x & 0o77;
59+
x | 0o123;
60+
x ^ 0o377;
61+
x &= 0o777;
62+
x |= 0o7;
63+
x ^= 0o70;
64+
65+
// GOOD: Bitwise operation, binary/hex/octal literal, two literals
66+
0b1010 & 0b1101;
67+
0xD ^ 0xF;
68+
0o377 ^ 0o77;
69+
0b1101 ^ 0xFF;
70+
71+
// GOOD: Numeric operation, any literal
72+
x += 99;
73+
x -= 0b1010;
74+
x *= 0xD;
75+
99 + 99;
76+
0b1010 - 0b1101;
77+
0xD * 0xD;
78+
79+
// BAD: Unary, cast and reference, decimal literal
80+
x & !100; //~ decimal_bitwise_operands
81+
x & -100; //~ decimal_bitwise_operands
82+
x & (100 as i32); //~ decimal_bitwise_operands
83+
x & &100; //~ decimal_bitwise_operands
84+
85+
// GOOD: Unary, cast and reference, non-decimal literal
86+
x & !0b1101;
87+
x & -0xD;
88+
x & (0o333 as i32);
89+
x & &0b1010;
90+
91+
// GOOD: Bitwise operation, variables only
92+
let y = 0;
93+
x & y;
94+
x &= y;
95+
x + y;
96+
x += y;
97+
98+
// GOOD: Macro expansion (should be ignored)
99+
bitwise_op!(x, 123);
100+
bitwise_op!(0b1010, 123);
101+
102+
// GOOD: Using const (should be ignored)
103+
x & SOME_CONST;
104+
x |= SOME_CONST;
105+
106+
// GOOD: Parenthesized binary/hex literal (should not trigger lint)
107+
x & (0b1111);
108+
x |= (0b1010);
109+
x ^ (/* comment */0b1100);
110+
(0xFF) & x;
111+
112+
// GOOD: Power of two and power of two minus one
113+
x & 16; // 2^4
114+
x | (31); // 2^5 - 1
115+
x ^ 0x40; // 2^6 (hex)
116+
x ^= 7; // 2^3 - 1
117+
118+
// GOOD: Bitwise operation, single digit decimal literal
119+
5 & 9;
120+
x ^ 6;
121+
x ^= 7;
122+
123+
// GOOD: More complex expressions
124+
(x + 1) & 0xFF;
125+
(x * 2) | (y & 0xF);
126+
(x ^ y) & 0b11110000;
127+
x | (1 << 9);
128+
129+
// GOOD: Special cases
130+
x & 0; // All bits off
131+
x | !0; // All bits on
132+
x ^ 1; // Toggle LSB
133+
}

0 commit comments

Comments
 (0)