[clang-tools-extra] [clang-tidy] Detect std::rot[lr] pattern within modernize.use-std-bit (PR #186324)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 12 23:30:41 PDT 2026
https://github.com/serge-sans-paille created https://github.com/llvm/llvm-project/pull/186324
Basically turning `x << N | x >> (64 - N)` into `std::rotl(x, N)`.
>From c538d7b048cb029fbb9d01382233ab23b394a148 Mon Sep 17 00:00:00 2001
From: serge-sans-paille <sguelton at mozilla.com>
Date: Thu, 12 Mar 2026 15:54:49 +0100
Subject: [PATCH] [clang-tidy] Detect std::rot[lr] pattern within
modernize.use-std-bit
Basically turning `x << N | x >> (64 - N)` into `std::rotl(x, N)`.
---
.../clang-tidy/modernize/UseStdBitCheck.cpp | 61 ++++++++++++++++++-
.../checks/modernize/use-std-bit.rst | 2 +
.../checkers/modernize/use-std-bit.cpp | 56 +++++++++++++++++
3 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
index e649c1c5aadda..e477bbdf47327 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -8,6 +8,7 @@
#include "UseStdBitCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/Support/FormatVariadic.h"
using namespace clang::ast_matchers;
@@ -37,7 +38,10 @@ void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
const auto LogicalAnd = MakeCommutativeBinaryOperatorMatcher("&&");
const auto Sub = MakeBinaryOperatorMatcher("-");
+ const auto ShiftLeft = MakeBinaryOperatorMatcher("<<");
+ const auto ShiftRight = MakeBinaryOperatorMatcher(">>");
const auto BitwiseAnd = MakeCommutativeBinaryOperatorMatcher("&");
+ const auto BitwiseOr = MakeCommutativeBinaryOperatorMatcher("|");
const auto CmpNot = MakeCommutativeBinaryOperatorMatcher("!=");
const auto CmpGt = MakeBinaryOperatorMatcher(">");
const auto CmpGte = MakeBinaryOperatorMatcher(">=");
@@ -87,6 +91,15 @@ void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
hasArgument(0, expr(hasType(isUnsignedInteger())).bind("v")))))
.bind("popcount_expr"),
this);
+
+ // Rotating an integer by a fixed amount
+ Finder->addMatcher(
+ BitwiseOr(ShiftLeft(BindDeclRef("v"),
+ integerLiteral().bind("shift_left_amount")),
+ ShiftRight(BoundDeclRef("v"),
+ integerLiteral().bind("shift_right_amount")))
+ .bind("rotate_expr"),
+ this);
}
void UseStdBitCheck::registerPPCallbacks(const SourceManager &SM,
@@ -141,9 +154,51 @@ void UseStdBitCheck::check(const MatchFinder::MatchResult &Result) {
<< IncludeInserter.createIncludeInsertion(
Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
}
- } else {
- llvm_unreachable("unexpected match");
+ } else if (const auto *MatchedExpr =
+ Result.Nodes.getNodeAs<BinaryOperator>("rotate_expr")) {
+ const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v");
+ const auto ShiftLeftAmount =
+ Result.Nodes.getNodeAs<IntegerLiteral>("shift_left_amount")->getValue();
+ const auto ShiftRightAmount =
+ Result.Nodes.getNodeAs<IntegerLiteral>("shift_right_amount")
+ ->getValue();
+ const uint64_t MatchedVarSize =
+ Context.getTypeSize(MatchedVarDecl->getType());
+
+ // Overflowing shifts
+ if (ShiftLeftAmount.sge(MatchedVarSize))
+ return;
+ if (ShiftRightAmount.sge(MatchedVarSize))
+ return;
+ // Not a rotation.
+ if (MatchedVarSize != (ShiftLeftAmount + ShiftRightAmount))
+ return;
+
+ const bool NeedsIntCast =
+ MatchedExpr->getType() != MatchedVarDecl->getType();
+ const bool isRotl = ShiftRightAmount.sge(ShiftLeftAmount);
+
+ const StringRef ReplacementFuncName = isRotl ? "rotl" : "rotr";
+ const uint64_t ReplacementShiftAmount =
+ (isRotl ? ShiftLeftAmount : ShiftRightAmount).getZExtValue();
+ auto Diag = diag(MatchedExpr->getBeginLoc(), "use 'std::%0' instead")
+ << ReplacementFuncName;
+ if (auto R = MatchedExpr->getSourceRange();
+ !R.getBegin().isMacroID() && !R.getEnd().isMacroID()) {
+ Diag << FixItHint::CreateReplacement(
+ MatchedExpr->getSourceRange(),
+ llvm::formatv("{3}std::{0}({1}, {2})", ReplacementFuncName,
+ MatchedVarDecl->getName(),
+ ReplacementShiftAmount,
+ NeedsIntCast ? "(int)" : "")
+ .str())
+ << IncludeInserter.createIncludeInsertion(
+ Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+ }
+
+ } else {
+ llvm_unreachable("unexpected match");
+ }
}
-}
} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst
index 26ef1b0841654..0428a48c653fe 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst
@@ -15,4 +15,6 @@ Expression Replacement
``(x != 0) && !(x & (x - 1))`` ``std::has_one_bit(x)``
``(x > 0) && !(x & (x - 1))`` ``std::has_one_bit(x)``
``std::bitset<N>(x).count()`` ``std::popcount(x)``
+``x << 3 | x >> 61`` ``std::rotl(x, 3)``
+``x << 61 | x >> 3`` ``std::rotr(x, 3)``
============================== =======================
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
index 52c725282f33c..0392fb9b8ec63 100644
--- 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
@@ -195,3 +195,59 @@ unsigned invalid_popcount_bitset(unsigned x, signed y) {
};
}
+
+/*
+ * rotate patterns
+ */
+unsigned char rotate_left_pattern(unsigned char x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotl' instead [modernize-use-std-bit]
+ // CHECK-FIXES: return (int)std::rotl(x, 3);
+ return x << 3 | x >> 5;
+}
+unsigned char rotate_left_pattern_perm(unsigned char x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotl' instead [modernize-use-std-bit]
+ // CHECK-FIXES: return (int)std::rotl(x, 3);
+ return x >> 5 | x << 3;
+}
+unsigned char rotate_swap_pattern(unsigned char x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotl' instead [modernize-use-std-bit]
+ // CHECK-FIXES: return (int)std::rotl(x, 4);
+ return x << 4 | x >> 4;
+}
+unsigned char rotate_right_pattern(unsigned char x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotr' instead [modernize-use-std-bit]
+ // CHECK-FIXES: return (int)std::rotr(x, 3);
+ return x << 5 | x >> 3;
+}
+unsigned char rotate_right_pattern_perm(unsigned char x) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotr' instead [modernize-use-std-bit]
+ // CHECK-FIXES: return (int)std::rotr(x, 3);
+ return x >> 3 | x << 5;
+}
+
+#define ROTR v >> 3 | v << 5
+unsigned char rotate_macro(unsigned char v) {
+ // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::rotr' instead [modernize-use-std-bit]
+ // No fixes, it comes from macro expansion.
+ return ROTR;
+}
+
+/*
+ * Invalid rotate patterns
+ */
+void invalid_rotate_patterns(unsigned char x, signed char y, unsigned char z) {
+ int patterns[] = {
+ // non-matching references
+ x >> 3 | z << 5,
+ // bad shift combination
+ x >> 3 | x << 6,
+ x >> 4 | x << 3,
+ // bad operator combination
+ x << 3 | x << 6,
+ x + 3 | x << 6,
+ x >> 3 & x << 5,
+ x >> 5 ^ x << 3,
+ // unsupported types
+ y >> 4 | y << 4,
+ };
+}
More information about the cfe-commits
mailing list