llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-tidy Author: Denis Mikhailov (denzor200) <details> <summary>Changes</summary> Add new clang-tidy check that 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. Bitwise operations on booleans can incur unnecessary performance overhead due to implicit integer conversions and missed short-circuit evaluation. Small example: ``` bool invalid = false; invalid |= x > limit.x; // warning: use logical operator instead of bitwise one for bool invalid |= y > limit.y; // warning: use logical operator instead of bitwise one for bool invalid |= z > limit.z; // warning: use logical operator instead of bitwise one for bool if (invalid) { // error handling } ``` Fixes: https://github.com/llvm/llvm-project/issues/40307 --- Patch is 61.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/142324.diff 10 Files Affected: - (added) clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.cpp (+200) - (added) clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.h (+38) - (modified) clang-tools-extra/clang-tidy/performance/CMakeLists.txt (+1) - (modified) clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp (+3) - (modified) clang-tools-extra/docs/ReleaseNotes.rst (+7) - (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1) - (added) clang-tools-extra/docs/clang-tidy/checks/performance/bool-bitwise-operation.rst (+40) - (added) clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-change-possible-side-effect.cpp (+83) - (added) clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-nontraditional.cpp (+388) - (added) clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation.cpp (+388) ``````````diff diff --git a/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.cpp b/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.cpp new file mode 100644 index 0000000000000..148ba65de129d --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.cpp @@ -0,0 +1,200 @@ +//===--- BoolBitwiseOperationCheck.cpp - clang-tidy -----------------------===// +// +// 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::performance { +namespace { +bool hasExplicitParentheses(const Expr *E, const SourceManager &SM, + const LangOptions &LangOpts) { + if (!E) + return false; + + const SourceLocation Start = E->getBeginLoc(); + const SourceLocation End = E->getEndLoc(); + + if (Start.isMacroID() || End.isMacroID() || !Start.isValid() || + !End.isValid()) { + return false; + } + + const std::optional<Token> PrevTok = + Lexer::findPreviousToken(Start, SM, LangOpts, /*IncludeComments=*/false); + const std::optional<Token> NextTok = + Lexer::findNextToken(End, SM, LangOpts, /*IncludeComments=*/false); + + return (PrevTok && PrevTok->is(tok::l_paren)) && + (NextTok && NextTok->is(tok::r_paren)); +} + +template <typename AstNode> +bool isInTemplateFunction(const AstNode *AN, ASTContext &Context) { + auto Parents = Context.getParents(*AN); + for (const auto &Parent : Parents) { + if (const auto *FD = Parent.template get<FunctionDecl>()) { + return FD->isTemplateInstantiation() || + FD->getTemplatedKind() != FunctionDecl::TK_NonTemplate; + } + if (const auto *S = Parent.template get<Stmt>()) { + return isInTemplateFunction(S, Context); + } + } + return false; +} + +constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 8U> + OperatorsTransformation{{{"|", "||"}, + {"|=", "||"}, + {"&", "&&"}, + {"&=", "&&"}, + {"bitand", "and"}, + {"and_eq", "and"}, + {"bitor", "or"}, + {"or_eq", "or"}}}; + +llvm::StringRef translate(llvm::StringRef Value) { + for (const auto &[Bitwise, Logical] : OperatorsTransformation) { + if (Value == Bitwise) + return Logical; + } + + return {}; +} +} // namespace + +BoolBitwiseOperationCheck::BoolBitwiseOperationCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), ChangePossibleSideEffects(Options.get( + "ChangePossibleSideEffects", false)) {} + +void BoolBitwiseOperationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ChangePossibleSideEffects", ChangePossibleSideEffects); +} + +void BoolBitwiseOperationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + binaryOperator( + unless(isExpansionInSystemHeader()), + hasAnyOperatorName("|", "&", "|=", "&="), + hasEitherOperand(expr(ignoringImpCasts(hasType(booleanType())))), + optionally(hasAncestor( // to simple implement transformations like + // `a&&b|c` -> `a&&(b||c)` + binaryOperator().bind("p")))) + .bind("op"), + this); +} + +void BoolBitwiseOperationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = Result.Nodes.getNodeAs<BinaryOperator>("op"); + + auto Diag = diag(MatchedExpr->getOperatorLoc(), + "use logical operator instead of bitwise one for bool"); + + if (isInTemplateFunction(MatchedExpr, *Result.Context)) + return; + + const bool HasVolatileOperand = llvm::any_of( + std::array{MatchedExpr->getLHS(), MatchedExpr->getRHS()}, + [](const Expr *E) { + return E->IgnoreImpCasts()->getType().isVolatileQualified(); + }); + const bool HasSideEffects = MatchedExpr->getRHS()->HasSideEffects( + *Result.Context, !ChangePossibleSideEffects); + if (HasVolatileOperand || HasSideEffects) + return; + + SourceLocation Loc = MatchedExpr->getOperatorLoc(); + + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + Loc = Result.SourceManager->getSpellingLoc(Loc); + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc); + if (TokenRange.isInvalid()) + return; + + StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager, + Result.Context->getLangOpts()); + StringRef TranslatedSpelling = translate(Spelling); + + if (TranslatedSpelling.empty()) + return; + + std::string FixSpelling = TranslatedSpelling.str(); + + FixItHint InsertEqual, ReplaceOperator, InsertBrace1, InsertBrace2; + if (MatchedExpr->isCompoundAssignmentOp()) { + const auto *DelcRefLHS = + dyn_cast<DeclRefExpr>(MatchedExpr->getLHS()->IgnoreImpCasts()); + if (!DelcRefLHS) + return; + const SourceLocation LocLHS = DelcRefLHS->getEndLoc(); + if (LocLHS.isInvalid() || LocLHS.isMacroID()) + return; + const SourceLocation InsertLoc = clang::Lexer::getLocForEndOfToken( + LocLHS, 0, *Result.SourceManager, Result.Context->getLangOpts()); + if (InsertLoc.isInvalid() || InsertLoc.isMacroID()) { + return; + } + InsertEqual = FixItHint::CreateInsertion( + InsertLoc, " = " + DelcRefLHS->getDecl()->getNameAsString()); + } + ReplaceOperator = FixItHint::CreateReplacement(TokenRange, FixSpelling); + + std::optional<BinaryOperatorKind> ParentOpcode; + if (const auto *Parent = Result.Nodes.getNodeAs<BinaryOperator>("p"); Parent) + ParentOpcode = Parent->getOpcode(); + + const auto *RHS = + dyn_cast<BinaryOperator>(MatchedExpr->getRHS()->IgnoreParenCasts()); + std::optional<BinaryOperatorKind> RHSOpcode; + if (RHS) + RHSOpcode = RHS->getOpcode(); + + const BinaryOperator *SurroundedExpr = nullptr; + if ((MatchedExpr->getOpcode() == BO_Or && ParentOpcode.has_value() && + *ParentOpcode == BO_LAnd) || + (MatchedExpr->getOpcode() == BO_And && ParentOpcode.has_value() && + llvm::is_contained({BO_Xor, BO_Or}, *ParentOpcode))) { + SurroundedExpr = MatchedExpr; + } else if (MatchedExpr->getOpcode() == BO_AndAssign && + RHSOpcode.has_value() && *RHSOpcode == BO_LOr) { + SurroundedExpr = RHS; + } + + if (hasExplicitParentheses(SurroundedExpr, *Result.SourceManager, + Result.Context->getLangOpts())) + SurroundedExpr = nullptr; + + if (SurroundedExpr) { + const SourceLocation InsertFirstLoc = SurroundedExpr->getBeginLoc(); + const SourceLocation InsertSecondLoc = clang::Lexer::getLocForEndOfToken( + SurroundedExpr->getEndLoc(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + if (InsertFirstLoc.isInvalid() || InsertFirstLoc.isMacroID() || + InsertSecondLoc.isInvalid() || InsertSecondLoc.isMacroID()) + return; + InsertBrace1 = FixItHint::CreateInsertion(InsertFirstLoc, "("); + InsertBrace2 = FixItHint::CreateInsertion(InsertSecondLoc, ")"); + } + + Diag << InsertEqual << ReplaceOperator << InsertBrace1 << InsertBrace2; +} + +} // namespace clang::tidy::performance diff --git a/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.h b/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.h new file mode 100644 index 0000000000000..6a5f0c7db6f1b --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/BoolBitwiseOperationCheck.h @@ -0,0 +1,38 @@ +//===--- BoolBitwiseOperationCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// 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_PERFORMANCE_BOOLBITWISEOPERATIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_BOOLBITWISEOPERATIONCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::performance { + +/// Finds potentially unefficient uses of bitwise operators such as `|`, +/// `&` and their compound analogues with `bool` type and suggests replacing +/// them with logical ones, like `||` and `&&`. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance/bool-bitwise-operation.html +class BoolBitwiseOperationCheck : public ClangTidyCheck { +public: + BoolBitwiseOperationCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Options) 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; + } + +private: + bool ChangePossibleSideEffects; +}; + +} // namespace clang::tidy::performance + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_BOOLBITWISEOPERATIONCHECK_H diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt index c6e547c5089fb..5a600e988d65e 100644 --- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangTidyPerformanceModule STATIC AvoidEndlCheck.cpp + BoolBitwiseOperationCheck.cpp EnumSizeCheck.cpp FasterStringFindCheck.cpp ForRangeCopyCheck.cpp diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp index 9e0fa6f88b36a..971951213aac1 100644 --- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -10,6 +10,7 @@ #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" #include "AvoidEndlCheck.h" +#include "BoolBitwiseOperationCheck.h" #include "EnumSizeCheck.h" #include "FasterStringFindCheck.h" #include "ForRangeCopyCheck.h" @@ -36,6 +37,8 @@ class PerformanceModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck<AvoidEndlCheck>("performance-avoid-endl"); + CheckFactories.registerCheck<BoolBitwiseOperationCheck>( + "performance-bool-bitwise-operation"); CheckFactories.registerCheck<EnumSizeCheck>("performance-enum-size"); CheckFactories.registerCheck<FasterStringFindCheck>( "performance-faster-string-find"); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index e0f81a032c38d..6499ed554968f 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -136,6 +136,13 @@ New checks Finds unintended character output from ``unsigned char`` and ``signed char`` to an ``ostream``. +- New :doc:`performance-bool-bitwise-operation + <clang-tidy/checks/performance/bool-bitwise-operation>` check. + + Finds potentially unefficient uses of bitwise operators such as ``|``, + ``&`` and their compound analogues with ``bool`` type and suggests replacing + them with logical ones, like ``||`` and ``&&``. + - New :doc:`portability-avoid-pragma-once <clang-tidy/checks/portability/avoid-pragma-once>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 5a79d61b1fd7e..19f96afb06cc0 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -333,6 +333,7 @@ Clang-Tidy Checks :doc:`openmp-exception-escape <openmp/exception-escape>`, :doc:`openmp-use-default-none <openmp/use-default-none>`, :doc:`performance-avoid-endl <performance/avoid-endl>`, "Yes" + :doc:`performance-bool-bitwise-operation <performance/bool-bitwise-operation>`, "Yes" :doc:`performance-enum-size <performance/enum-size>`, :doc:`performance-faster-string-find <performance/faster-string-find>`, "Yes" :doc:`performance-for-range-copy <performance/for-range-copy>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/bool-bitwise-operation.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/bool-bitwise-operation.rst new file mode 100644 index 0000000000000..762e57352db66 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/performance/bool-bitwise-operation.rst @@ -0,0 +1,40 @@ +.. title:: clang-tidy - performance-bool-bitwise-operation + +performance-bool-bitwise-operation +================================== + +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. Bitwise +operations on booleans can incur unnecessary performance overhead +due to implicit integer conversions and missed short-circuit evaluation. + +.. code-block:: c++ + + bool invalid = false; + invalid |= x > limit.x; // warning: use logical operator instead of bitwise one for bool + invalid |= y > limit.y; // warning: use logical operator instead of bitwise one for bool + invalid |= z > limit.z; // warning: use logical operator instead of bitwise one for bool + if (invalid) { + // error handling + } + +These 3 warnings suggest to assign result of logical ``||`` operation instead of using ``|=`` operator: + +.. code-block:: c++ + + bool invalid = false; + invalid = invalid || x > limit.x; + invalid = invalid || y > limit.x; + invalid = invalid || z > limit.z; + if (invalid) { + // error handling + } + +Options +------- + +.. option:: ChangePossibleSideEffects + + Enabling this option promotes more fixit hints even when they might + change evaluation order or skip side effects. Default value is `false`. \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-change-possible-side-effect.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-change-possible-side-effect.cpp new file mode 100644 index 0000000000000..b22f504d56b4c --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-change-possible-side-effect.cpp @@ -0,0 +1,83 @@ +// RUN: %check_clang_tidy %s performance-bool-bitwise-operation %t \ +// RUN: -config="{CheckOptions: { \ +// RUN: performance-bool-bitwise-operation.ChangePossibleSideEffects: true }}" + +bool function_with_possible_side_effects(); + +void bad_possible_side_effects() { + bool a = true, b = false; + + a | function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a || function_with_possible_side_effects(); + + a & function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a && function_with_possible_side_effects(); + + a |= function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a || function_with_possible_side_effects(); + + a &= function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a && function_with_possible_side_effects(); + + bool c = true; + + a &= function_with_possible_side_effects() && c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a && function_with_possible_side_effects() && c; + + a &= b && function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a && b && function_with_possible_side_effects(); + + a |= function_with_possible_side_effects() || c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a || function_with_possible_side_effects() || c; + + a |= b || function_with_possible_side_effects(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-FIXES: a = a || b || function_with_possible_side_effects(); +} + +void bad_definitely_side_effects() { + bool a = true, b = false; + int acc = 0; + + a | (acc++, b); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a & (acc++, b); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a |= (acc++, b); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a &= (acc++, b); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + bool c = true; + + a &= (acc++, b) && c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a &= b && (acc++, c); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a |= (acc++, b) || c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes + + a |= b || (acc++, c); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use logical operator instead of bitwise one for bool [performance-bool-bitwise-operation] + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:{{.*}}: note: FIX-IT applied suggested code changes +} + diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-nontraditional.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-nontraditional.cpp new file mode 100644 index 0000000000000..e1463a290453d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/bool-bitwise-operation-nontraditional.cpp @@ -0,0 +1,388 @@ +// RUN: %check_clang_tidy %s performance-bool-bitwise-operation %t + +bool& normal() { + int a = 100, b = 200; + + a bitor b; + a bitand b; + a or_eq b; + a and_eq b; + + static bool st = false; + return st; +} + +bool bad() noexcept __att... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/142324 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits