[clang-tools-extra] [clang-tidy] New checker: modernize.use-std-bit to detect std::has_one_bit idiom (PR #185435)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 9 07:52:20 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra
Author: None (serge-sans-paille)
<details>
<summary>Changes</summary>
>From https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
---
Full diff: https://github.com/llvm/llvm-project/pull/185435.diff
7 Files Affected:
- (modified) clang-tools-extra/clang-tidy/modernize/CMakeLists.txt (+1)
- (modified) clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp (+2)
- (added) clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp (+84)
- (added) clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h (+43)
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5)
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
- (added) clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp (+50)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index cc4cc7a02b594..2c5c44db587fe 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -47,6 +47,7 @@ add_clang_library(clangTidyModernizeModule STATIC
UseRangesCheck.cpp
UseScopedLockCheck.cpp
UseStartsEndsWithCheck.cpp
+ UseStdBitCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
UseStdPrintCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index fcb860d8c5298..cc13da7535bcb 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -47,6 +47,7 @@
#include "UseRangesCheck.h"
#include "UseScopedLockCheck.h"
#include "UseStartsEndsWithCheck.h"
+#include "UseStdBitCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
#include "UseStdPrintCheck.h"
@@ -96,6 +97,7 @@ class ModernizeModule : public ClangTidyModule {
"modernize-use-scoped-lock");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
+ CheckFactories.registerCheck<UseStdBitCheck>("modernize-use-std-bit");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
CheckFactories.registerCheck<UseStdNumbersCheck>(
"modernize-use-std-numbers");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
new file mode 100644
index 0000000000000..57014ab88a094
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdBitCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
+ const auto makeBinaryOperatorMatcher = [](auto Op) {
+ return [=](auto LHS, auto RHS) {
+ return binaryOperator(
+ hasOperatorName(Op),
+ hasOperands(ignoringParenImpCasts(LHS), ignoringParenImpCasts(RHS)));
+ };
+ };
+
+ const auto logicalAnd = makeBinaryOperatorMatcher("&&");
+ const auto sub = makeBinaryOperatorMatcher("-");
+ const auto bitwiseAnd = makeBinaryOperatorMatcher("&");
+ const auto cmpNot = makeBinaryOperatorMatcher("!=");
+ const auto cmpGt = makeBinaryOperatorMatcher(">");
+
+ const auto logicalNot = [](auto Expr) {
+ return unaryOperator(hasOperatorName("!"),
+ hasUnaryOperand(ignoringParenImpCasts(Expr)));
+ };
+
+ const auto isNonNull = [=](auto Expr) {
+ return anyOf(Expr, cmpNot(Expr, integerLiteral(equals(0))),
+ cmpGt(Expr, integerLiteral(equals(0))));
+ };
+ const auto bindDeclRef = [](auto Name) {
+ return declRefExpr(to(varDecl(hasType(isUnsignedInteger())).bind(Name)));
+ };
+ const auto boundDeclRef = [](auto Name) {
+ return declRefExpr(to(varDecl(equalsBoundNode(Name))));
+ };
+
+ // https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
+ // has_one_bit(v) = v && !(v & (v - 1));
+ Finder->addMatcher(
+ logicalAnd(isNonNull(bindDeclRef("v")),
+ logicalNot(bitwiseAnd(
+ boundDeclRef("v"),
+ sub(boundDeclRef("v"), integerLiteral(equals(1))))))
+ .bind("expr"),
+ this);
+}
+
+void UseStdBitCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdBitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
+}
+
+void UseStdBitCheck::check(const MatchFinder::MatchResult &Result) {
+ const ASTContext &Context = *Result.Context;
+ const SourceManager &Source = Context.getSourceManager();
+
+ const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v");
+ const auto *MatchedExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
+
+ diag(MatchedExpr->getBeginLoc(), "use std::has_one_bit instead")
+ << MatchedVarDecl->getName()
+ << FixItHint::CreateReplacement(
+ MatchedExpr->getSourceRange(),
+ ("std::has_one_bit(" + MatchedVarDecl->getName() + ")").str())
+ << IncludeInserter.createIncludeInsertion(
+ Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h
new file mode 100644
index 0000000000000..e09a3cf35fe18
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::modernize {
+
+/// use function from <bit> instead of common idioms.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-bit.html
+class UseStdBitCheck : public ClangTidyCheck {
+public:
+ UseStdBitCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus20;
+ }
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+ utils::IncludeInserter IncludeInserter;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 9f4d7e6923fa0..9fdf0c93a4e3f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -124,6 +124,11 @@ New checks
``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
+- New :doc:`modernize-use-std-bit
+ <clang-tidy/checks/modernize/use-std-bit>` check.
+
+ Use functions from ``<bit>`` instead of common idioms.
+
- New :doc:`modernize-use-string-view
<clang-tidy/checks/modernize/use-string-view>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 068431fb5c94c..956e495eb4645 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -326,6 +326,7 @@ Clang-Tidy Checks
:doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
:doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
+ :doc:`modernize-use-std-bit <modernize/use-std-bit>`, "Yes"
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
:doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp
new file mode 100644
index 0000000000000..0ef978ff386ce
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp
@@ -0,0 +1,50 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-std-bit %t
+// CHECK-FIXES: #include <bit>
+
+unsigned bithacks(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return x && !(x & (x - 1));
+}
+
+unsigned long bithacks(unsigned long x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return x && !(x & (x - 1));
+}
+
+unsigned short bithacks(unsigned short x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return x && !(x & (x - 1));
+}
+
+unsigned bithacks_perm(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return x && !((x - 1) & (x));
+}
+
+unsigned bithacks_variant_neq(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return (x != 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_neq_perm(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return (x != 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_gt(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return (x > 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_gt_perm(unsigned x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+ // CHECK-FIXES: return std::has_one_bit(x);
+ return (x > 0) && !(x & (x - 1));
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/185435
More information about the cfe-commits
mailing list