From 68a7fd22ad39e1d7a50f2fd052696517e719dd18 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 31 May 2022 22:14:43 -0400 Subject: [PATCH] Move `BitMask` into `Operators` lint pass --- clippy_lints/src/lib.register_all.rs | 4 +- clippy_lints/src/lib.register_correctness.rs | 4 +- clippy_lints/src/lib.register_lints.rs | 6 +- clippy_lints/src/lib.register_pedantic.rs | 2 +- clippy_lints/src/lib.rs | 6 +- clippy_lints/src/{ => operators}/bit_mask.rs | 168 ++---------------- clippy_lints/src/operators/mod.rs | 111 +++++++++++- .../src/operators/verbose_bit_mask.rs | 44 +++++ 8 files changed, 179 insertions(+), 166 deletions(-) rename clippy_lints/src/{ => operators}/bit_mask.rs (51%) create mode 100644 clippy_lints/src/operators/verbose_bit_mask.rs diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index b43c11330f0d..ccb5357b86eb 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -15,8 +15,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), - LintId::of(bit_mask::BAD_BIT_MASK), - LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(blacklisted_name::BLACKLISTED_NAME), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), @@ -259,6 +257,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::ASSIGN_OP_PATTERN), + LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::INEFFECTIVE_BIT_MASK), LintId::of(operators::MISREFACTORED_ASSIGN_OP), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 26852445fdcd..76286e93493e 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -8,8 +8,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::USELESS_ATTRIBUTE), - LintId::of(bit_mask::BAD_BIT_MASK), - LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(booleans::LOGIC_BUG), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), @@ -51,6 +49,8 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), + LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::INEFFECTIVE_BIT_MASK), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 231511d46b72..1bcba0bc988b 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -54,9 +54,6 @@ store.register_lints(&[ await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE, await_holding_invalid::AWAIT_HOLDING_LOCK, await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, - bit_mask::BAD_BIT_MASK, - bit_mask::INEFFECTIVE_BIT_MASK, - bit_mask::VERBOSE_BIT_MASK, blacklisted_name::BLACKLISTED_NAME, blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS, bool_assert_comparison::BOOL_ASSERT_COMPARISON, @@ -434,9 +431,12 @@ store.register_lints(&[ open_options::NONSENSICAL_OPEN_OPTIONS, operators::ABSURD_EXTREME_COMPARISONS, operators::ASSIGN_OP_PATTERN, + operators::BAD_BIT_MASK, operators::FLOAT_ARITHMETIC, + operators::INEFFECTIVE_BIT_MASK, operators::INTEGER_ARITHMETIC, operators::MISREFACTORED_ASSIGN_OP, + operators::VERBOSE_BIT_MASK, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index a8b6c5a5d63f..b1603afa25fd 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,7 +4,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(bit_mask::VERBOSE_BIT_MASK), LintId::of(borrow_as_ptr::BORROW_AS_PTR), LintId::of(bytecount::NAIVE_BYTECOUNT), LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), @@ -76,6 +75,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING), LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES), LintId::of(non_expressive_names::SIMILAR_NAMES), + LintId::of(operators::VERBOSE_BIT_MASK), LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(ranges::RANGE_MINUS_ONE), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0f9e3b062331..f8362998b8ce 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -177,7 +177,6 @@ mod assertions_on_constants; mod async_yields_async; mod attrs; mod await_holding_invalid; -mod bit_mask; mod blacklisted_name; mod blocks_in_if_conditions; mod bool_assert_comparison; @@ -582,8 +581,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(eq_op::EqOp)); store.register_late_pass(|| Box::new(enum_clike::UnportableVariant)); store.register_late_pass(|| Box::new(float_literal::FloatLiteral)); - let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; - store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold))); store.register_late_pass(|| Box::new(ptr::Ptr)); store.register_late_pass(|| Box::new(ptr_eq::PtrEq)); store.register_late_pass(|| Box::new(needless_bool::NeedlessBool)); @@ -936,7 +933,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv))); store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv))); - store.register_late_pass(|| Box::new(operators::Operators::default())); + let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; + store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs similarity index 51% rename from clippy_lints/src/bit_mask.rs rename to clippy_lints/src/operators/bit_mask.rs index dc7e400fdc28..74387fbc87be 100644 --- a/clippy_lints/src/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -1,161 +1,23 @@ use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::sugg::Sugg; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; +use clippy_utils::diagnostics::span_lint; use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_lint::LateContext; use rustc_span::source_map::Span; -declare_clippy_lint! { - /// ### What it does - /// Checks for incompatible bit masks in comparisons. - /// - /// The formula for detecting if an expression of the type `_ m - /// c` (where `` is one of {`&`, `|`} and `` is one of - /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following - /// table: - /// - /// |Comparison |Bit Op|Example |is always|Formula | - /// |------------|------|-------------|---------|----------------------| - /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` | - /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | - /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | - /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` | - /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` | - /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` | - /// - /// ### Why is this bad? - /// If the bits that the comparison cares about are always - /// set to zero or one by the bit mask, the comparison is constant `true` or - /// `false` (depending on mask, compared value, and operators). - /// - /// So the code is actively misleading, and the only reason someone would write - /// this intentionally is to win an underhanded Rust contest or create a - /// test-case for this lint. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if (x & 1 == 2) { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub BAD_BIT_MASK, - correctness, - "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" -} +use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK}; -declare_clippy_lint! { - /// ### What it does - /// Checks for bit masks in comparisons which can be removed - /// without changing the outcome. The basic structure can be seen in the - /// following table: - /// - /// |Comparison| Bit Op |Example |equals | - /// |----------|----------|------------|-------| - /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`| - /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`| - /// - /// ### Why is this bad? - /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask), - /// but still a bit misleading, because the bit mask is ineffective. - /// - /// ### Known problems - /// False negatives: This lint will only match instances - /// where we have figured out the math (which is for a power-of-two compared - /// value). This means things like `x | 1 >= 7` (which would be better written - /// as `x >= 6`) will not be reported (but bit masks like this are fairly - /// uncommon). - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if (x | 1 > 3) { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INEFFECTIVE_BIT_MASK, - correctness, - "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for bit masks that can be replaced by a call - /// to `trailing_zeros` - /// - /// ### Why is this bad? - /// `x.trailing_zeros() > 4` is much clearer than `x & 15 - /// == 0` - /// - /// ### Known problems - /// llvm generates better code for `x & 15 == 0` on x86 - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if x & 0b1111 == 0 { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub VERBOSE_BIT_MASK, - pedantic, - "expressions where a bit mask is less readable than the corresponding method call" -} - -#[derive(Copy, Clone)] -pub struct BitMask { - verbose_bit_mask_threshold: u64, -} - -impl BitMask { - #[must_use] - pub fn new(verbose_bit_mask_threshold: u64) -> Self { - Self { - verbose_bit_mask_threshold, - } - } -} - -impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]); - -impl<'tcx> LateLintPass<'tcx> for BitMask { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::Binary(cmp, left, right) = &e.kind { - if cmp.node.is_comparison() { - if let Some(cmp_opt) = fetch_int_literal(cx, right) { - check_compare(cx, left, cmp.node, cmp_opt, e.span); - } else if let Some(cmp_val) = fetch_int_literal(cx, left) { - check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span); - } - } - } - - if let ExprKind::Binary(op, left, right) = &e.kind - && BinOpKind::Eq == op.node - && let ExprKind::Binary(op1, left1, right1) = &left.kind - && BinOpKind::BitAnd == op1.node - && let ExprKind::Lit(lit) = &right1.kind - && let LitKind::Int(n, _) = lit.node - && let ExprKind::Lit(lit1) = &right.kind - && let LitKind::Int(0, _) = lit1.node - && n.leading_zeros() == n.count_zeros() - && n > u128::from(self.verbose_bit_mask_threshold) - { - span_lint_and_then( - cx, - VERBOSE_BIT_MASK, - e.span, - "bit mask could be simplified with a call to `trailing_zeros`", - |diag| { - let sugg = Sugg::hir(cx, left1, "...").maybe_par(); - diag.span_suggestion( - e.span, - "try", - format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), - Applicability::MaybeIncorrect, - ); - }, - ); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if op.is_comparison() { + if let Some(cmp_opt) = fetch_int_literal(cx, right) { + check_compare(cx, left, op, cmp_opt, e.span); + } else if let Some(cmp_val) = fetch_int_literal(cx, left) { + check_compare(cx, right, invert_cmp(op), cmp_val, e.span); } } } diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index c1d038aa2b1a..fd91fbd63a45 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -4,8 +4,10 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; mod absurd_extreme_comparisons; mod assign_op_pattern; +mod bit_mask; mod misrefactored_assign_op; mod numeric_arithmetic; +mod verbose_bit_mask; declare_clippy_lint! { /// ### What it does @@ -146,9 +148,103 @@ declare_clippy_lint! { "having a variable on both sides of an assign op" } -#[derive(Default)] +declare_clippy_lint! { + /// ### What it does + /// Checks for incompatible bit masks in comparisons. + /// + /// The formula for detecting if an expression of the type `_ m + /// c` (where `` is one of {`&`, `|`} and `` is one of + /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following + /// table: + /// + /// |Comparison |Bit Op|Example |is always|Formula | + /// |------------|------|-------------|---------|----------------------| + /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` | + /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | + /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | + /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` | + /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` | + /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` | + /// + /// ### Why is this bad? + /// If the bits that the comparison cares about are always + /// set to zero or one by the bit mask, the comparison is constant `true` or + /// `false` (depending on mask, compared value, and operators). + /// + /// So the code is actively misleading, and the only reason someone would write + /// this intentionally is to win an underhanded Rust contest or create a + /// test-case for this lint. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if (x & 1 == 2) { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub BAD_BIT_MASK, + correctness, + "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bit masks in comparisons which can be removed + /// without changing the outcome. The basic structure can be seen in the + /// following table: + /// + /// |Comparison| Bit Op |Example |equals | + /// |----------|----------|------------|-------| + /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`| + /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`| + /// + /// ### Why is this bad? + /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask), + /// but still a bit misleading, because the bit mask is ineffective. + /// + /// ### Known problems + /// False negatives: This lint will only match instances + /// where we have figured out the math (which is for a power-of-two compared + /// value). This means things like `x | 1 >= 7` (which would be better written + /// as `x >= 6`) will not be reported (but bit masks like this are fairly + /// uncommon). + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if (x | 1 > 3) { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INEFFECTIVE_BIT_MASK, + correctness, + "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bit masks that can be replaced by a call + /// to `trailing_zeros` + /// + /// ### Why is this bad? + /// `x.trailing_zeros() > 4` is much clearer than `x & 15 + /// == 0` + /// + /// ### Known problems + /// llvm generates better code for `x & 15 == 0` on x86 + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if x & 0b1111 == 0 { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub VERBOSE_BIT_MASK, + pedantic, + "expressions where a bit mask is less readable than the corresponding method call" +} + pub struct Operators { arithmetic_context: numeric_arithmetic::Context, + verbose_bit_mask_threshold: u64, } impl_lint_pass!(Operators => [ ABSURD_EXTREME_COMPARISONS, @@ -156,7 +252,18 @@ impl_lint_pass!(Operators => [ FLOAT_ARITHMETIC, ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP, + BAD_BIT_MASK, + INEFFECTIVE_BIT_MASK, + VERBOSE_BIT_MASK, ]); +impl Operators { + pub fn new(verbose_bit_mask_threshold: u64) -> Self { + Self { + arithmetic_context: numeric_arithmetic::Context::default(), + verbose_bit_mask_threshold, + } + } +} impl<'tcx> LateLintPass<'tcx> for Operators { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { match e.kind { @@ -165,6 +272,8 @@ impl<'tcx> LateLintPass<'tcx> for Operators { absurd_extreme_comparisons::check(cx, e, op.node, lhs, rhs); } self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); + bit_mask::check(cx, e, op.node, lhs, rhs); + verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold); }, ExprKind::AssignOp(op, lhs, rhs) => { self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); diff --git a/clippy_lints/src/operators/verbose_bit_mask.rs b/clippy_lints/src/operators/verbose_bit_mask.rs new file mode 100644 index 000000000000..ff85fd554298 --- /dev/null +++ b/clippy_lints/src/operators/verbose_bit_mask.rs @@ -0,0 +1,44 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::VERBOSE_BIT_MASK; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + threshold: u64, +) { + if BinOpKind::Eq == op + && let ExprKind::Binary(op1, left1, right1) = &left.kind + && BinOpKind::BitAnd == op1.node + && let ExprKind::Lit(lit) = &right1.kind + && let LitKind::Int(n, _) = lit.node + && let ExprKind::Lit(lit1) = &right.kind + && let LitKind::Int(0, _) = lit1.node + && n.leading_zeros() == n.count_zeros() + && n > u128::from(threshold) + { + span_lint_and_then( + cx, + VERBOSE_BIT_MASK, + e.span, + "bit mask could be simplified with a call to `trailing_zeros`", + |diag| { + let sugg = Sugg::hir(cx, left1, "...").maybe_par(); + diag.span_suggestion( + e.span, + "try", + format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), + Applicability::MaybeIncorrect, + ); + }, + ); + } +}