Skip to content

Commit 2c95550

Browse files
authored
New lint: decimal_bitwise_operands (#15215)
Add a new lint that detects the use of decimal literals as bit masks in bitwise operations. Using decimal literals for bit masks can obscure the intended bit pattern and reduce code readability. This lint encourages the use of binary (`0b...`) or hexadecimal (`0x...`) notation to make bit patterns explicit and easier to understand at a glance. Example: ```rust let masked = x & 14; // Bad: decimal literal as bit mask let masked = x & 0b1110; // Good: bit pattern is explicit ``` changelog: [`decimal_bitwise_operands`]: new lint Fixes #1775
2 parents 92b4b68 + 9cb6306 commit 2c95550

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)