1+ // ===--- BoolBitwiseOperationCheck.cpp - clang-tidy -----------------------===//
2+ //
3+ // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+ // See https://llvm.org/LICENSE.txt for license information.
5+ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+ //
7+ // ===----------------------------------------------------------------------===//
8+
9+ #include " BoolBitwiseOperationCheck.h"
10+ #include " clang/ASTMatchers/ASTMatchFinder.h"
11+ #include " clang/Lex/Lexer.h"
12+ #include < array>
13+ #include < optional>
14+ #include < utility>
15+
16+ using namespace clang ::ast_matchers;
17+
18+ namespace clang ::tidy::misc {
19+
20+ static const NamedDecl *
21+ getLHSNamedDeclIfCompoundAssign (const BinaryOperator *BO) {
22+ if (BO->isCompoundAssignmentOp ()) {
23+ const auto *DeclRefLHS =
24+ dyn_cast<DeclRefExpr>(BO->getLHS ()->IgnoreImpCasts ());
25+ return DeclRefLHS ? DeclRefLHS->getDecl () : nullptr ;
26+ }
27+ return nullptr ;
28+ }
29+
30+ constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 8U >
31+ OperatorsTransformation{{{" |" , " ||" },
32+ {" |=" , " ||" },
33+ {" &" , " &&" },
34+ {" &=" , " &&" },
35+ {" bitand" , " and" },
36+ {" and_eq" , " and" },
37+ {" bitor" , " or" },
38+ {" or_eq" , " or" }}};
39+
40+ static llvm::StringRef translate (llvm::StringRef Value) {
41+ for (const auto &[Bitwise, Logical] : OperatorsTransformation) {
42+ if (Value == Bitwise)
43+ return Logical;
44+ }
45+
46+ return {};
47+ }
48+
49+ BoolBitwiseOperationCheck::BoolBitwiseOperationCheck (StringRef Name,
50+ ClangTidyContext *Context)
51+ : ClangTidyCheck(Name, Context),
52+ StrictMode (Options.get(" StrictMode" , false )),
53+ IgnoreMacros(Options.get(" IgnoreMacros" , false )) {}
54+
55+ void BoolBitwiseOperationCheck::storeOptions (
56+ ClangTidyOptions::OptionMap &Opts) {
57+ Options.store (Opts, " StrictMode" , StrictMode);
58+ Options.store (Opts, " IgnoreMacros" , IgnoreMacros);
59+ }
60+
61+ void BoolBitwiseOperationCheck::registerMatchers (MatchFinder *Finder) {
62+ Finder->addMatcher (
63+ binaryOperator (
64+ unless (isExpansionInSystemHeader ()),
65+ hasAnyOperatorName (" |" , " &" , " |=" , " &=" ),
66+ hasEitherOperand (expr (hasType (booleanType ()))),
67+ optionally (hasParent ( // to simple implement transformations like
68+ // `a&&b|c` -> `a&&(b||c)`
69+ binaryOperator ().bind (" p" ))))
70+ .bind (" op" ),
71+ this );
72+ }
73+
74+ void BoolBitwiseOperationCheck::check (const MatchFinder::MatchResult &Result) {
75+ const auto *MatchedExpr = Result.Nodes .getNodeAs <BinaryOperator>(" op" );
76+
77+ auto DiagEmitter = [MatchedExpr, this ] {
78+ const NamedDecl *ND = getLHSNamedDeclIfCompoundAssign (MatchedExpr);
79+ return diag (MatchedExpr->getOperatorLoc (),
80+ " use logical operator '%0' for boolean %select{variable "
81+ " %2|values}1 instead of bitwise operator '%3'" )
82+ << translate (MatchedExpr->getOpcodeStr ()) << (ND == nullptr ) << ND
83+ << MatchedExpr->getOpcodeStr ();
84+ };
85+
86+ const bool HasVolatileOperand = llvm::any_of (
87+ std::array{MatchedExpr->getLHS (), MatchedExpr->getRHS ()},
88+ [](const Expr *E) {
89+ return E->IgnoreImpCasts ()->getType ().isVolatileQualified ();
90+ });
91+ if (HasVolatileOperand)
92+ return static_cast <void >(DiagEmitter ());
93+
94+ const bool HasSideEffects = MatchedExpr->getRHS ()->HasSideEffects (
95+ *Result.Context , /* IncludePossibleEffects=*/ !StrictMode);
96+ if (HasSideEffects)
97+ return static_cast <void >(DiagEmitter ());
98+
99+ SourceLocation Loc = MatchedExpr->getOperatorLoc ();
100+
101+ if (Loc.isInvalid () || Loc.isMacroID ())
102+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
103+
104+ Loc = Result.SourceManager ->getSpellingLoc (Loc);
105+ if (Loc.isInvalid () || Loc.isMacroID ())
106+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
107+
108+ const CharSourceRange TokenRange = CharSourceRange::getTokenRange (Loc);
109+ if (TokenRange.isInvalid ())
110+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
111+
112+ const StringRef FixSpelling = translate (Lexer::getSourceText (
113+ TokenRange, *Result.SourceManager , Result.Context ->getLangOpts ()));
114+
115+ if (FixSpelling.empty ())
116+ return static_cast <void >(DiagEmitter ());
117+
118+ FixItHint InsertEqual;
119+ if (MatchedExpr->isCompoundAssignmentOp ()) {
120+ const auto *DeclRefLHS =
121+ dyn_cast<DeclRefExpr>(MatchedExpr->getLHS ()->IgnoreImpCasts ());
122+ if (!DeclRefLHS)
123+ return static_cast <void >(DiagEmitter ());
124+ const SourceLocation LocLHS = DeclRefLHS->getEndLoc ();
125+ if (LocLHS.isInvalid () || LocLHS.isMacroID ())
126+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
127+ const SourceLocation InsertLoc = clang::Lexer::getLocForEndOfToken (
128+ LocLHS, 0 , *Result.SourceManager , Result.Context ->getLangOpts ());
129+ if (InsertLoc.isInvalid () || InsertLoc.isMacroID ())
130+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
131+ InsertEqual = FixItHint::CreateInsertion (
132+ InsertLoc, " = " + DeclRefLHS->getDecl ()->getNameAsString ());
133+ }
134+
135+ auto ReplaceOperator = FixItHint::CreateReplacement (TokenRange, FixSpelling);
136+
137+ const auto *Parent = Result.Nodes .getNodeAs <BinaryOperator>(" p" );
138+ std::optional<BinaryOperatorKind> ParentOpcode;
139+ if (Parent)
140+ ParentOpcode = Parent->getOpcode ();
141+
142+ const auto *RHS =
143+ dyn_cast<BinaryOperator>(MatchedExpr->getRHS ()->IgnoreImpCasts ());
144+ std::optional<BinaryOperatorKind> RHSOpcode;
145+ if (RHS)
146+ RHSOpcode = RHS->getOpcode ();
147+
148+ const Expr *SurroundedExpr = nullptr ;
149+ if ((MatchedExpr->getOpcode () == BO_Or && ParentOpcode == BO_LAnd) ||
150+ (MatchedExpr->getOpcode () == BO_And &&
151+ llvm::is_contained ({BO_Xor, BO_Or}, ParentOpcode))) {
152+ const Expr *Side = Parent->getLHS ()->IgnoreParenImpCasts () == MatchedExpr
153+ ? Parent->getLHS ()
154+ : Parent->getRHS ();
155+ SurroundedExpr = Side->IgnoreImpCasts ();
156+ assert (SurroundedExpr->IgnoreParens () == MatchedExpr);
157+ } else if (MatchedExpr->getOpcode () == BO_AndAssign && RHSOpcode == BO_LOr)
158+ SurroundedExpr = RHS;
159+
160+ if (SurroundedExpr && isa<ParenExpr>(SurroundedExpr))
161+ SurroundedExpr = nullptr ;
162+
163+ FixItHint InsertBrace1;
164+ FixItHint InsertBrace2;
165+ if (SurroundedExpr) {
166+ const SourceLocation InsertFirstLoc = SurroundedExpr->getBeginLoc ();
167+ const SourceLocation InsertSecondLoc = clang::Lexer::getLocForEndOfToken (
168+ SurroundedExpr->getEndLoc (), 0 , *Result.SourceManager ,
169+ Result.Context ->getLangOpts ());
170+ if (InsertFirstLoc.isInvalid () || InsertFirstLoc.isMacroID () ||
171+ InsertSecondLoc.isInvalid () || InsertSecondLoc.isMacroID ())
172+ return static_cast <void >(IgnoreMacros || DiagEmitter ());
173+ InsertBrace1 = FixItHint::CreateInsertion (InsertFirstLoc, " (" );
174+ InsertBrace2 = FixItHint::CreateInsertion (InsertSecondLoc, " )" );
175+ }
176+
177+ DiagEmitter () << InsertEqual << ReplaceOperator << InsertBrace1
178+ << InsertBrace2;
179+ }
180+
181+ } // namespace clang::tidy::misc
0 commit comments