-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[clang-tidy] Add misc-bool-bitwise-operation check #167552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 28 commits
710b7b4
0a1b47f
5bc7f73
14dcf62
52844e3
d7ae305
793dc97
6e523d8
352d118
0d4aa3b
dfa7ce4
cb9d520
6457ade
7c6552f
e2b7c86
2aaa928
75a5399
c01dfbe
af75a05
aaf2612
96dc087
123aa63
aff7d53
52066b7
a4c4487
7c069da
4e7cdc4
b3af196
d0a3b18
e335116
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,291 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 "BoolBitwiseOperationCheck.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/Lex/Lexer.h" | ||
| #include <array> | ||
| #include <optional> | ||
| #include <utility> | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::misc { | ||
|
|
||
| static const DynTypedNode *ignoreParensTowardsTheRoot(const DynTypedNode *N, | ||
| ASTContext *AC) { | ||
| if (const auto *S = N->get<Stmt>(); isa_and_nonnull<ParenExpr>(S)) { | ||
| auto Parents = AC->getParents(*S); | ||
| // FIXME: do we need to consider all `Parents` ? | ||
| if (!Parents.empty()) | ||
| return ignoreParensTowardsTheRoot(&Parents[0], AC); | ||
| } | ||
| return N; | ||
| } | ||
|
|
||
| static bool assignsToBoolean(const BinaryOperator *BinOp, ASTContext *AC) { | ||
| TraversalKindScope RAII(*AC, TK_AsIs); | ||
| auto Parents = AC->getParents(*BinOp); | ||
|
|
||
| return llvm::any_of(Parents, [&](const DynTypedNode &Parent) { | ||
| const auto *S = ignoreParensTowardsTheRoot(&Parent, AC)->get<Stmt>(); | ||
| const auto *ICE = dyn_cast_if_present<ImplicitCastExpr>(S); | ||
| return ICE ? ICE->getType().getDesugaredType(*AC)->isBooleanType() : false; | ||
| }); | ||
| } | ||
|
|
||
| static constexpr std::array<std::pair<StringRef, StringRef>, 8U> | ||
denzor200 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| OperatorsTransformation{{{"|", "||"}, | ||
| {"|=", "||"}, | ||
| {"&", "&&"}, | ||
| {"&=", "&&"}, | ||
| {"bitand", "and"}, | ||
| {"and_eq", "and"}, | ||
| {"bitor", "or"}, | ||
| {"or_eq", "or"}}}; | ||
|
|
||
| static StringRef translate(StringRef Value) { | ||
| for (const auto &[Bitwise, Logical] : OperatorsTransformation) { | ||
| if (Value == Bitwise) | ||
| return Logical; | ||
| } | ||
|
|
||
| return {}; | ||
| } | ||
|
|
||
| static bool isBooleanBitwise(const BinaryOperator *BinOp, ASTContext *AC, | ||
| std::optional<bool> &RootAssignsToBoolean) { | ||
| if (!BinOp) | ||
| return false; | ||
|
|
||
| if (!llvm::is_contained(llvm::make_first_range(OperatorsTransformation), | ||
| BinOp->getOpcodeStr())) | ||
| return false; | ||
|
|
||
| bool IsBooleanLHS = BinOp->getLHS() | ||
| ->IgnoreImpCasts() | ||
| ->getType() | ||
| .getDesugaredType(*AC) | ||
| ->isBooleanType(); | ||
denzor200 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| bool IsBooleanRHS = BinOp->getRHS() | ||
| ->IgnoreImpCasts() | ||
| ->getType() | ||
| .getDesugaredType(*AC) | ||
| ->isBooleanType(); | ||
| if (IsBooleanLHS && IsBooleanRHS) { | ||
| RootAssignsToBoolean = RootAssignsToBoolean.value_or(false); | ||
| return true; | ||
| } | ||
| if (((IsBooleanLHS || IsBooleanRHS) && assignsToBoolean(BinOp, AC)) || | ||
| RootAssignsToBoolean.value_or(false)) { | ||
| RootAssignsToBoolean = RootAssignsToBoolean.value_or(true); | ||
| return true; | ||
| } | ||
| if (BinOp->isCompoundAssignmentOp() && IsBooleanLHS) { | ||
| RootAssignsToBoolean = RootAssignsToBoolean.value_or(true); | ||
| return true; | ||
| } | ||
|
|
||
| std::optional<bool> DummyFlag = false; | ||
| IsBooleanLHS = IsBooleanLHS || | ||
| isBooleanBitwise(dyn_cast<BinaryOperator>( | ||
| BinOp->getLHS()->IgnoreParenImpCasts()), | ||
| AC, DummyFlag); | ||
| IsBooleanRHS = IsBooleanRHS || | ||
| isBooleanBitwise(dyn_cast<BinaryOperator>( | ||
| BinOp->getRHS()->IgnoreParenImpCasts()), | ||
| AC, DummyFlag); | ||
|
|
||
| if (IsBooleanLHS && IsBooleanRHS) { | ||
| RootAssignsToBoolean = RootAssignsToBoolean.value_or(false); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| static const Expr *getAcceptableCompoundsLHS(const BinaryOperator *BinOp) { | ||
| assert(BinOp->isCompoundAssignmentOp()); | ||
| const Expr *LHS = BinOp->getLHS()->IgnoreImpCasts(); | ||
| return isa<DeclRefExpr, MemberExpr>(LHS) ? LHS : nullptr; | ||
| } | ||
|
|
||
| BoolBitwiseOperationCheck::BoolBitwiseOperationCheck(StringRef Name, | ||
| ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| UnsafeMode(Options.get("UnsafeMode", false)), | ||
| IgnoreMacros(Options.get("IgnoreMacros", false)) {} | ||
|
|
||
| void BoolBitwiseOperationCheck::storeOptions( | ||
| ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "UnsafeMode", UnsafeMode); | ||
| Options.store(Opts, "IgnoreMacros", IgnoreMacros); | ||
| } | ||
|
|
||
| void BoolBitwiseOperationCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| binaryOperator(unless(isExpansionInSystemHeader()), | ||
| unless(hasParent(binaryOperator())) // ignoring parenExpr | ||
| ) | ||
| .bind("binOpRoot"), | ||
| this); | ||
|
Comment on lines
+129
to
+134
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can write matchers like this to remove auto M1 = binaryOperator().bind(...);
// as sub-expressions of ImplicitCastExpr or ParenExpr
auto M2 = expr(
anyOf(implicitCastExpr().bind(...), parenExpr()),
ignoringParenImpCasts(M1),
unless(hasParent(anyOf(implicitCastExpr(), parenExpr()))));
// Other cases, including as full expressions
auto M3 = expr(M1, unless(hasParent(anyOf(implicitCastExpr(), parenExpr(), binaryOperator()))));
// I'm not sure if `TK_AsIs` is needed
->addMatcher(expr(unless(isExpansionInSystemHeader()), anyOf(M2, M3)), this);They should be given better names but I have no idea about it.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we can't do it because with |
||
| } | ||
|
|
||
| void BoolBitwiseOperationCheck::emitWarningAndChangeOperatorsIfPossible( | ||
| const BinaryOperator *BinOp, const BinaryOperator *ParentBinOp, | ||
| const clang::SourceManager &SM, clang::ASTContext &Ctx) { | ||
| auto DiagEmitter = [BinOp, this] { | ||
| return diag(BinOp->getOperatorLoc(), | ||
| "use logical operator '%0' for boolean semantics instead of " | ||
| "bitwise operator '%1'") | ||
| << translate(BinOp->getOpcodeStr()) << BinOp->getOpcodeStr(); | ||
| }; | ||
|
|
||
| const bool HasVolatileOperand = llvm::any_of( | ||
| std::array{BinOp->getLHS(), BinOp->getRHS()}, [&](const Expr *E) { | ||
| return E->IgnoreImpCasts() | ||
| ->getType() | ||
| .getDesugaredType(Ctx) | ||
| .isVolatileQualified(); | ||
| }); | ||
| if (HasVolatileOperand) { | ||
| DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| const bool HasSideEffects = BinOp->getRHS()->HasSideEffects( | ||
| Ctx, /*IncludePossibleEffects=*/!UnsafeMode); | ||
| if (HasSideEffects) { | ||
| DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| SourceLocation Loc = BinOp->getOperatorLoc(); | ||
|
|
||
| if (Loc.isInvalid() || Loc.isMacroID()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| Loc = SM.getSpellingLoc(Loc); | ||
| if (Loc.isInvalid() || Loc.isMacroID()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc); | ||
| if (TokenRange.isInvalid()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| const StringRef FixSpelling = | ||
| translate(Lexer::getSourceText(TokenRange, SM, Ctx.getLangOpts())); | ||
|
|
||
| if (FixSpelling.empty()) { | ||
| DiagEmitter(); | ||
| return; | ||
| } | ||
|
|
||
| FixItHint InsertEqual; | ||
| if (BinOp->isCompoundAssignmentOp()) { | ||
| const auto *LHS = getAcceptableCompoundsLHS(BinOp); | ||
| if (!LHS) { | ||
| DiagEmitter(); | ||
| return; | ||
| } | ||
| const SourceLocation LocLHS = LHS->getEndLoc(); | ||
| if (LocLHS.isInvalid() || LocLHS.isMacroID()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
| const SourceLocation InsertLoc = | ||
| clang::Lexer::getLocForEndOfToken(LocLHS, 0, SM, Ctx.getLangOpts()); | ||
| if (InsertLoc.isInvalid() || InsertLoc.isMacroID()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
| auto SourceText = static_cast<std::string>(Lexer::getSourceText( | ||
| CharSourceRange::getTokenRange(LHS->getSourceRange()), SM, | ||
| Ctx.getLangOpts())); | ||
| llvm::erase_if(SourceText, | ||
| [](unsigned char Ch) { return std::isspace(Ch); }); | ||
| InsertEqual = FixItHint::CreateInsertion(InsertLoc, " = " + SourceText); | ||
| } | ||
|
|
||
| auto ReplaceOperator = FixItHint::CreateReplacement(TokenRange, FixSpelling); | ||
|
|
||
| std::optional<BinaryOperatorKind> ParentOpcode; | ||
| if (ParentBinOp) | ||
| ParentOpcode = ParentBinOp->getOpcode(); | ||
|
|
||
| const auto *RHS = dyn_cast<BinaryOperator>(BinOp->getRHS()->IgnoreImpCasts()); | ||
| std::optional<BinaryOperatorKind> RHSOpcode; | ||
| if (RHS) | ||
| RHSOpcode = RHS->getOpcode(); | ||
|
|
||
| const Expr *SurroundedExpr = nullptr; | ||
| if ((BinOp->getOpcode() == BO_Or && ParentOpcode == BO_LAnd) || | ||
| (BinOp->getOpcode() == BO_And && | ||
| llvm::is_contained({BO_Xor, BO_Or}, ParentOpcode))) { | ||
| const Expr *Side = ParentBinOp->getLHS()->IgnoreParenImpCasts() == BinOp | ||
| ? ParentBinOp->getLHS() | ||
| : ParentBinOp->getRHS(); | ||
| SurroundedExpr = Side->IgnoreImpCasts(); | ||
| assert(SurroundedExpr->IgnoreParens() == BinOp); | ||
| } else if (BinOp->getOpcode() == BO_AndAssign && RHSOpcode == BO_LOr) | ||
| SurroundedExpr = RHS; | ||
|
|
||
| if (isa_and_nonnull<ParenExpr>(SurroundedExpr)) | ||
| SurroundedExpr = nullptr; | ||
|
|
||
| FixItHint InsertBrace1; | ||
| FixItHint InsertBrace2; | ||
| if (SurroundedExpr) { | ||
| const SourceLocation InsertFirstLoc = SurroundedExpr->getBeginLoc(); | ||
| const SourceLocation InsertSecondLoc = clang::Lexer::getLocForEndOfToken( | ||
| SurroundedExpr->getEndLoc(), 0, SM, Ctx.getLangOpts()); | ||
| if (InsertFirstLoc.isInvalid() || InsertFirstLoc.isMacroID() || | ||
| InsertSecondLoc.isInvalid() || InsertSecondLoc.isMacroID()) { | ||
| IgnoreMacros || DiagEmitter(); | ||
| return; | ||
| } | ||
| InsertBrace1 = FixItHint::CreateInsertion(InsertFirstLoc, "("); | ||
| InsertBrace2 = FixItHint::CreateInsertion(InsertSecondLoc, ")"); | ||
| } | ||
|
|
||
| DiagEmitter() << InsertEqual << ReplaceOperator << InsertBrace1 | ||
| << InsertBrace2; | ||
| } | ||
|
|
||
| void BoolBitwiseOperationCheck::visitBinaryTreesNode( | ||
| const BinaryOperator *BinOp, const BinaryOperator *ParentBinOp, | ||
| const clang::SourceManager &SM, clang::ASTContext &Ctx, | ||
| std::optional<bool> &RootAssignsToBoolean) { | ||
| if (!BinOp) | ||
| return; | ||
|
|
||
| if (isBooleanBitwise(BinOp, &Ctx, RootAssignsToBoolean)) | ||
| emitWarningAndChangeOperatorsIfPossible(BinOp, ParentBinOp, SM, Ctx); | ||
|
|
||
| visitBinaryTreesNode( | ||
| dyn_cast<BinaryOperator>(BinOp->getLHS()->IgnoreParenImpCasts()), BinOp, | ||
| SM, Ctx, RootAssignsToBoolean); | ||
| visitBinaryTreesNode( | ||
| dyn_cast<BinaryOperator>(BinOp->getRHS()->IgnoreParenImpCasts()), BinOp, | ||
| SM, Ctx, RootAssignsToBoolean); | ||
| } | ||
|
|
||
| void BoolBitwiseOperationCheck::check(const MatchFinder::MatchResult &Result) { | ||
| const auto *BinOpRoot = Result.Nodes.getNodeAs<BinaryOperator>("binOpRoot"); | ||
| const SourceManager &SM = *Result.SourceManager; | ||
| ASTContext &Ctx = *Result.Context; | ||
| std::optional<bool> RootAssignsToBoolean = std::nullopt; | ||
| visitBinaryTreesNode(BinOpRoot, nullptr, SM, Ctx, RootAssignsToBoolean); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::misc | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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_MISC_BOOLBITWISEOPERATIONCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLBITWISEOPERATIONCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::misc { | ||
|
|
||
| /// Finds potentially inefficient use of bitwise operators such as ``&``, ``|`` | ||
| /// and their compound analogues on Boolean values where logical operators like | ||
| /// ``&&`` and ``||`` would be more appropriate. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// https://clang.llvm.org/extra/clang-tidy/checks/misc/bool-bitwise-operation.html | ||
| class BoolBitwiseOperationCheck : public ClangTidyCheck { | ||
| public: | ||
| BoolBitwiseOperationCheck(StringRef Name, ClangTidyContext *Context); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| 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.CPlusPlus || LangOpts.C99; | ||
| } | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
|
|
||
| private: | ||
| void emitWarningAndChangeOperatorsIfPossible( | ||
| const BinaryOperator *BinOp, const BinaryOperator *ParentBinOp, | ||
| const clang::SourceManager &SM, clang::ASTContext &Ctx); | ||
| void visitBinaryTreesNode(const BinaryOperator *BinOp, | ||
| const BinaryOperator *ParentBinOp, | ||
| const clang::SourceManager &SM, | ||
| clang::ASTContext &Ctx, | ||
| std::optional<bool> &RootAssignsToBoolean); | ||
|
|
||
| bool UnsafeMode; | ||
| bool IgnoreMacros; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::misc | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLBITWISEOPERATIONCHECK_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using
getCanonicalType()?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need more time for me to understand the difference between DesugaredType and CanonicalType