Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6280,6 +6280,7 @@ Released 2018-09-13
[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`decimal_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_bit_mask
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
[`default_constructed_unit_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs
Expand Down
106 changes: 106 additions & 0 deletions clippy_lints/src/decimal_bit_mask.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::SpanRangeExt;
use rustc_data_structures::packed::Pu128;
use rustc_hir::{AssignOpKind, BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::source_map::Spanned;

declare_clippy_lint! {
/// ### What it does
/// Checks for decimal literals used as bit masks in bitwise operations.
///
/// ### Why is this bad?
/// Using decimal literals for bit masks can make the code less readable and obscure the intended bit pattern.
/// Binary or hexadecimal or octal literals make the bit pattern more explicit and easier to understand at a glance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Binary or hexadecimal or octal literals make the bit pattern more explicit and easier to understand at a glance.
/// Binary, hexadecimal, or octal literals make the bit pattern more explicit and easier to understand at a glance.

///
/// ### Example
/// ```rust,no_run
/// let a = 14 & 6; // Bit pattern is not immediately clear
/// ```
/// Use instead:
/// ```rust,no_run
/// let a = 0b1110 & 0b0110;
/// ```
#[clippy::version = "1.87.0"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be updated^^

pub DECIMAL_BIT_MASK,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to say this earlier, but lint names should be in plural, see https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints

nursery,
"use binary, hex or octal literals for bitwise operations"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"use binary, hex or octal literals for bitwise operations"
"use binary, hex, or octal literals for bitwise operations"

}

declare_lint_pass!(DecimalBitMask => [DECIMAL_BIT_MASK]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint would actually fit quite well into the operators group I think. You can take a look at the lints in the operators/ directory for an example of how to integrate a lint into a group


fn check_bitwise_binary_expr(cx: &LateContext<'_>, e: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::BitAnd | BinOpKind::BitOr | BinOpKind::BitXor,
..
},
left,
right,
) = &e.kind
{
for expr in [left, right] {
if let ExprKind::Lit(_) = &expr.kind
&& !is_not_decimal_number(cx, expr.span)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The double negation is a bit confusing. Could you please invert the logic of is_not_decimal_number and rename it accordingly?

&& !is_power_of_twoish(expr)
{
emit_lint(cx, expr.span);
Comment on lines +44 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got really confused here:

  1. Usually, the convention is to call the main expression expr, and subexpressions, if any, e. But you do it the other way around, calling each subexpression (left and right) expr. Because of that, I almost thought you had a logic bug, due to calling is_not_decimal_number and emit_lint on the main expression instead of the subexpression. I'd be grateful if you could swap their names

  2. In this function, you call is_not_decimal_number and emit_lint on expr, while in check_bitwise_assign_expr, you call it on lit. Looking at the test suite (which is very extensive, thanks for that!), one can see the following diagnostics:

    error: using decimal literal for bitwise operation
      --> tests/ui/decimal_bit_mask.rs:18:9
       |
    LL |     x ^ (99);
       |         ^^^^
    
    error: using decimal literal for bitwise operation
      --> tests/ui/decimal_bit_mask.rs:21:11
       |
    LL |     x ^= (99);
       |           ^^
    

    So the assign-op case points at the number itself, as the span of lit apparently doesn't include the parens. I think that comes out a bit nicer, so I'd ask you to use lit in this function as well.

    Also, as the span the of the literal doesn't include the parens, maybe you might be able use starts_with in is_not_decimal_number after all?

}
}
}
}

fn check_bitwise_assign_expr(cx: &LateContext<'_>, e: &Expr<'_>) {
if let ExprKind::AssignOp(
Spanned {
node: AssignOpKind::BitAndAssign | AssignOpKind::BitOrAssign | AssignOpKind::BitXorAssign,
..
},
_,
expr @ Expr {
kind: ExprKind::Lit(lit),
..
},
) = &e.kind
Comment on lines +56 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very much a nit: imo, these big patterns are too verbose -- what do you think about something like this?

Suggested change
if let ExprKind::AssignOp(
Spanned {
node: AssignOpKind::BitAndAssign | AssignOpKind::BitOrAssign | AssignOpKind::BitXorAssign,
..
},
_,
expr @ Expr {
kind: ExprKind::Lit(lit),
..
},
) = &e.kind
if let ExprKind::AssignOp(op, _, expr) = &e.kind
&& matches!(op.node, AssignOpKind::BitAndAssign | AssignOpKind::BitOrAssign | AssignOpKind::BitXorAssign)
&& let ExprKind::Lit(lit) = expr.kind

&& !is_power_of_twoish(expr)
&& !is_not_decimal_number(cx, lit.span)
{
emit_lint(cx, lit.span);
}
}

fn is_not_decimal_number(cx: &LateContext<'_>, span: Span) -> bool {
span.check_source_text(cx, |src| src.contains("0b") || src.contains("0x") || src.contains("0o"))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, 0b1000 isn't recognized as being a binary literal, because the way you check for that is by looking at the span of the literal, but here the span actually contains the parentheses surrounding the literal. Because of that, you'll need to first trim starting paren(s), then possibly some whitespace, and only then performing the actual checks.

@ada4a I simplified this part (using contains) to handle parentheses, whitespaces and comments. It might miss some complex cases, but that’s an intentional trade-off. Such cases usually mean special constructs and skipping the lint seems more appropriate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! You might want to add a small comment about this (e.g. why you didn't use starts_with)

}

fn is_power_of_twoish(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = &expr.kind
&& let rustc_ast::LitKind::Int(Pu128(val), _) = lit.node
{
return val.is_power_of_two() || val.wrapping_add(1).is_power_of_two();
}
false
}

fn emit_lint(cx: &LateContext<'_>, span: Span) {
span_lint_and_help(
cx,
DECIMAL_BIT_MASK,
span,
"using decimal literal for bitwise operation",
None,
"use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability",
"use binary (0b...), hex (0x...), or octal (0o...) notation for better readability",

);
}

impl<'tcx> LateLintPass<'tcx> for DecimalBitMask {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
return;
}
check_bitwise_binary_expr(cx, e);
check_bitwise_assign_expr(cx, e);
}
}
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO,
crate::create_dir::CREATE_DIR_INFO,
crate::dbg_macro::DBG_MACRO_INFO,
crate::decimal_bit_mask::DECIMAL_BIT_MASK_INFO,
crate::default::DEFAULT_TRAIT_ACCESS_INFO,
crate::default::FIELD_REASSIGN_WITH_DEFAULT_INFO,
crate::default_constructed_unit_structs::DEFAULT_CONSTRUCTED_UNIT_STRUCTS_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ mod copy_iterator;
mod crate_in_macro_def;
mod create_dir;
mod dbg_macro;
mod decimal_bit_mask;
mod default;
mod default_constructed_unit_structs;
mod default_instead_of_iter_empty;
Expand Down Expand Up @@ -816,6 +817,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
store.register_late_pass(|_| Box::new(decimal_bit_mask::DecimalBitMask));
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default()));
Expand Down
101 changes: 101 additions & 0 deletions tests/ui/decimal_bit_mask.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#![allow(clippy::erasing_op)]
#![allow(clippy::no_effect)]
#![warn(clippy::decimal_bit_mask)]

macro_rules! bitwise_op {
($x:expr, $y:expr) => {
$x & $y;
};
}

pub const SOME_CONST: i32 = 12345;

fn main() {
let mut x = 0;
// BAD: Bitwise operation, decimal literal, one literal
x & 99; //~ decimal_bit_mask
x | (/* comment */99); //~ decimal_bit_mask
x ^ (99); //~ decimal_bit_mask
x &= 99; //~ decimal_bit_mask
x |= 99; //~ decimal_bit_mask
x ^= (99); //~ decimal_bit_mask

// BAD: Bitwise operation, decimal literal, two literals
0b1010 & 99; //~ decimal_bit_mask
0b1010 | 99; //~ decimal_bit_mask
0b1010 ^ 99; //~ decimal_bit_mask
99 & 0b1010; //~ decimal_bit_mask
99 | 0b1010; //~ decimal_bit_mask
99 ^ 0b1010; //~ decimal_bit_mask
0xD | 99; //~ decimal_bit_mask
88 & 99;
//~^ decimal_bit_mask
//~| decimal_bit_mask

// GOOD: Bitwise operation, binary/hex/octal literal, one literal
x & 0b1010;
x | 0b1010;
x ^ 0b1010;
x &= 0b1010;
x |= 0b1010;
x ^= 0b1010;
x & 0xD;
x & 0o77;
x | 0o123;
x ^ 0o377;
x &= 0o777;
x |= 0o7;
x ^= 0o70;

// GOOD: Bitwise operation, binary/hex/octal literal, two literals
0b1010 & 0b1101;
0xD ^ 0xF;
0o377 ^ 0o77;
0b1101 ^ 0xFF;

// GOOD: Numeric operation, any literal
x += 99;
x -= 0b1010;
x *= 0xD;
99 + 99;
0b1010 - 0b1101;
0xD * 0xD;

// GOOD: Bitwise operation, variables only
let y = 0;
x & y;
x &= y;
x + y;
x += y;

// GOOD: Macro expansion (should be ignored)
bitwise_op!(x, 123);
bitwise_op!(0b1010, 123);

// GOOD: Using const (should be ignored)
x & SOME_CONST;
x |= SOME_CONST;

// GOOD: Parenthesized binary/hex literal (should not trigger lint)
x & (0b1111);
x |= (0b1010);
x ^ (/* comment */0b1100);
(0xFF) & x;

// GOOD: Power of two and power of two minus one
x & 16; // 2^4
x | 31; // 2^5 - 1
x ^ 0x40; // 2^6 (hex)
x ^= 7; // 2^3 - 1

// GOOD: More complex expressions
(x + 1) & 0xFF;
(x * 2) | (y & 0xF);
(x ^ y) & 0b11110000;
x | (1 << 9);

// GOOD: Special cases
x & 0; // All bits off
x | !0; // All bits on
x ^ 1; // Toggle LSB
}
124 changes: 124 additions & 0 deletions tests/ui/decimal_bit_mask.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:16:9
|
LL | x & 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability
= note: `-D clippy::decimal-bit-mask` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::decimal_bit_mask)]`

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:17:9
|
LL | x | (/* comment */99);
| ^^^^^^^^^^^^^^^^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:18:9
|
LL | x ^ (99);
| ^^^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:19:10
|
LL | x &= 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:20:10
|
LL | x |= 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:21:11
|
LL | x ^= (99);
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:24:14
|
LL | 0b1010 & 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:25:14
|
LL | 0b1010 | 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:26:14
|
LL | 0b1010 ^ 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:27:5
|
LL | 99 & 0b1010;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:28:5
|
LL | 99 | 0b1010;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:29:5
|
LL | 99 ^ 0b1010;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:30:11
|
LL | 0xD | 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:31:5
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: using decimal literal for bitwise operation
--> tests/ui/decimal_bit_mask.rs:31:10
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b...) or hex (0x...) or octal (0o...) notation for better readability

error: aborting due to 15 previous errors

Empty file.