xref: /llvm-project/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp (revision 26078f33bdfa30f35bb880feb0c088b8bd2169c8)
1 //===--- OperatorsRepresentationCheck.cpp - clang-tidy
2 //--------------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "OperatorsRepresentationCheck.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/STLExtras.h"
16 #include <array>
17 #include <utility>
18 
19 using namespace clang::ast_matchers;
20 
21 namespace clang::tidy::readability {
22 
getOperatorSpelling(SourceLocation Loc,ASTContext & Context)23 static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {
24   if (Loc.isInvalid())
25     return {};
26 
27   SourceManager &SM = Context.getSourceManager();
28 
29   Loc = SM.getSpellingLoc(Loc);
30   if (Loc.isInvalid())
31     return {};
32 
33   const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
34   return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());
35 }
36 
37 namespace {
38 
AST_MATCHER_P2(BinaryOperator,hasInvalidBinaryOperatorRepresentation,BinaryOperatorKind,Kind,llvm::StringRef,ExpectedRepresentation)39 AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,
40                BinaryOperatorKind, Kind, llvm::StringRef,
41                ExpectedRepresentation) {
42   if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
43     return false;
44 
45   StringRef Spelling =
46       getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
47   return !Spelling.empty() && Spelling != ExpectedRepresentation;
48 }
49 
AST_MATCHER_P2(UnaryOperator,hasInvalidUnaryOperatorRepresentation,UnaryOperatorKind,Kind,llvm::StringRef,ExpectedRepresentation)50 AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
51                UnaryOperatorKind, Kind, llvm::StringRef,
52                ExpectedRepresentation) {
53   if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
54     return false;
55 
56   StringRef Spelling =
57       getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
58   return !Spelling.empty() && Spelling != ExpectedRepresentation;
59 }
60 
AST_MATCHER_P2(CXXOperatorCallExpr,hasInvalidOverloadedOperatorRepresentation,OverloadedOperatorKind,Kind,llvm::StringRef,ExpectedRepresentation)61 AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
62                OverloadedOperatorKind, Kind, llvm::StringRef,
63                ExpectedRepresentation) {
64   if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
65     return false;
66 
67   StringRef Spelling =
68       getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
69   return !Spelling.empty() && Spelling != ExpectedRepresentation;
70 }
71 
72 } // namespace
73 
74 constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>
75     UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};
76 
77 constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>
78     OperatorsRepresentation{{{"&&", "and"},
79                              {"||", "or"},
80                              {"^", "xor"},
81                              {"&", "bitand"},
82                              {"|", "bitor"},
83                              {"&=", "and_eq"},
84                              {"|=", "or_eq"},
85                              {"!=", "not_eq"},
86                              {"^=", "xor_eq"}}};
87 
translate(llvm::StringRef Value)88 static llvm::StringRef translate(llvm::StringRef Value) {
89   for (const auto &[Traditional, Alternative] : UnaryRepresentation) {
90     if (Value == Traditional)
91       return Alternative;
92     if (Value == Alternative)
93       return Traditional;
94   }
95 
96   for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
97     if (Value == Traditional)
98       return Alternative;
99     if (Value == Alternative)
100       return Traditional;
101   }
102   return {};
103 }
104 
isNotOperatorStr(llvm::StringRef Value)105 static bool isNotOperatorStr(llvm::StringRef Value) {
106   return translate(Value).empty();
107 }
108 
isSeparator(char C)109 static bool isSeparator(char C) noexcept {
110   constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");
111   return Separators.contains(C);
112 }
113 
needEscaping(llvm::StringRef Operator)114 static bool needEscaping(llvm::StringRef Operator) {
115   switch (Operator[0]) {
116   case '&':
117   case '|':
118   case '!':
119   case '^':
120   case '~':
121     return false;
122   default:
123     return true;
124   }
125 }
126 
127 static llvm::StringRef
getRepresentation(const std::vector<llvm::StringRef> & Config,llvm::StringRef Traditional,llvm::StringRef Alternative)128 getRepresentation(const std::vector<llvm::StringRef> &Config,
129                   llvm::StringRef Traditional, llvm::StringRef Alternative) {
130   if (llvm::is_contained(Config, Traditional))
131     return Traditional;
132   if (llvm::is_contained(Config, Alternative))
133     return Alternative;
134   return {};
135 }
136 
137 template <typename T>
isAnyOperatorEnabled(const std::vector<llvm::StringRef> & Config,const T & Operators)138 static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,
139                                  const T &Operators) {
140   for (const auto &[traditional, alternative] : Operators) {
141     if (!getRepresentation(Config, traditional, alternative).empty())
142       return true;
143   }
144   return false;
145 }
146 
OperatorsRepresentationCheck(StringRef Name,ClangTidyContext * Context)147 OperatorsRepresentationCheck::OperatorsRepresentationCheck(
148     StringRef Name, ClangTidyContext *Context)
149     : ClangTidyCheck(Name, Context),
150       BinaryOperators(
151           utils::options::parseStringList(Options.get("BinaryOperators", ""))),
152       OverloadedOperators(utils::options::parseStringList(
153           Options.get("OverloadedOperators", ""))) {
154   llvm::erase_if(BinaryOperators, isNotOperatorStr);
155   llvm::erase_if(OverloadedOperators, isNotOperatorStr);
156 }
157 
storeOptions(ClangTidyOptions::OptionMap & Opts)158 void OperatorsRepresentationCheck::storeOptions(
159     ClangTidyOptions::OptionMap &Opts) {
160   Options.store(Opts, "BinaryOperators",
161                 utils::options::serializeStringList(BinaryOperators));
162   Options.store(Opts, "OverloadedOperators",
163                 utils::options::serializeStringList(OverloadedOperators));
164 }
165 
166 std::optional<TraversalKind>
getCheckTraversalKind() const167 OperatorsRepresentationCheck::getCheckTraversalKind() const {
168   return TK_IgnoreUnlessSpelledInSource;
169 }
170 
isLanguageVersionSupported(const LangOptions & LangOpts) const171 bool OperatorsRepresentationCheck::isLanguageVersionSupported(
172     const LangOptions &LangOpts) const {
173   return LangOpts.CPlusPlus;
174 }
175 
registerBinaryOperatorMatcher(MatchFinder * Finder)176 void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177     MatchFinder *Finder) {
178   if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
179     return;
180 
181   Finder->addMatcher(
182       binaryOperator(
183           unless(isExpansionInSystemHeader()),
184           anyOf(hasInvalidBinaryOperatorRepresentation(
185                     BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),
186                 hasInvalidBinaryOperatorRepresentation(
187                     BO_LOr, getRepresentation(BinaryOperators, "||", "or")),
188                 hasInvalidBinaryOperatorRepresentation(
189                     BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),
190                 hasInvalidBinaryOperatorRepresentation(
191                     BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),
192                 hasInvalidBinaryOperatorRepresentation(
193                     BO_And, getRepresentation(BinaryOperators, "&", "bitand")),
194                 hasInvalidBinaryOperatorRepresentation(
195                     BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),
196                 hasInvalidBinaryOperatorRepresentation(
197                     BO_AndAssign,
198                     getRepresentation(BinaryOperators, "&=", "and_eq")),
199                 hasInvalidBinaryOperatorRepresentation(
200                     BO_OrAssign,
201                     getRepresentation(BinaryOperators, "|=", "or_eq")),
202                 hasInvalidBinaryOperatorRepresentation(
203                     BO_XorAssign,
204                     getRepresentation(BinaryOperators, "^=", "xor_eq"))))
205           .bind("binary_op"),
206       this);
207 }
208 
registerUnaryOperatorMatcher(MatchFinder * Finder)209 void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210     MatchFinder *Finder) {
211   if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
212     return;
213 
214   Finder->addMatcher(
215       unaryOperator(
216           unless(isExpansionInSystemHeader()),
217           anyOf(hasInvalidUnaryOperatorRepresentation(
218                     UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
219                 hasInvalidUnaryOperatorRepresentation(
220                     UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
221           .bind("unary_op"),
222       this);
223 }
224 
registerOverloadedOperatorMatcher(MatchFinder * Finder)225 void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226     MatchFinder *Finder) {
227   if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
228       !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
229     return;
230 
231   Finder->addMatcher(
232       cxxOperatorCallExpr(
233           unless(isExpansionInSystemHeader()),
234           anyOf(
235               hasInvalidOverloadedOperatorRepresentation(
236                   OO_AmpAmp,
237                   getRepresentation(OverloadedOperators, "&&", "and")),
238               hasInvalidOverloadedOperatorRepresentation(
239                   OO_PipePipe,
240                   getRepresentation(OverloadedOperators, "||", "or")),
241               hasInvalidOverloadedOperatorRepresentation(
242                   OO_Exclaim,
243                   getRepresentation(OverloadedOperators, "!", "not")),
244               hasInvalidOverloadedOperatorRepresentation(
245                   OO_ExclaimEqual,
246                   getRepresentation(OverloadedOperators, "!=", "not_eq")),
247               hasInvalidOverloadedOperatorRepresentation(
248                   OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
249               hasInvalidOverloadedOperatorRepresentation(
250                   OO_Amp,
251                   getRepresentation(OverloadedOperators, "&", "bitand")),
252               hasInvalidOverloadedOperatorRepresentation(
253                   OO_Pipe,
254                   getRepresentation(OverloadedOperators, "|", "bitor")),
255               hasInvalidOverloadedOperatorRepresentation(
256                   OO_AmpEqual,
257                   getRepresentation(OverloadedOperators, "&=", "and_eq")),
258               hasInvalidOverloadedOperatorRepresentation(
259                   OO_PipeEqual,
260                   getRepresentation(OverloadedOperators, "|=", "or_eq")),
261               hasInvalidOverloadedOperatorRepresentation(
262                   OO_CaretEqual,
263                   getRepresentation(OverloadedOperators, "^=", "xor_eq")),
264               hasInvalidOverloadedOperatorRepresentation(
265                   OO_Tilde,
266                   getRepresentation(OverloadedOperators, "~", "compl"))))
267           .bind("overloaded_op"),
268       this);
269 }
270 
registerMatchers(MatchFinder * Finder)271 void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {
272   registerBinaryOperatorMatcher(Finder);
273   registerUnaryOperatorMatcher(Finder);
274   registerOverloadedOperatorMatcher(Finder);
275 }
276 
check(const MatchFinder::MatchResult & Result)277 void OperatorsRepresentationCheck::check(
278     const MatchFinder::MatchResult &Result) {
279 
280   SourceLocation Loc;
281 
282   if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
283     Loc = Op->getOperatorLoc();
284   else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))
285     Loc = Op->getOperatorLoc();
286   else if (const auto *Op =
287                Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))
288     Loc = Op->getOperatorLoc();
289 
290   if (Loc.isInvalid())
291     return;
292 
293   Loc = Result.SourceManager->getSpellingLoc(Loc);
294   if (Loc.isInvalid() || Loc.isMacroID())
295     return;
296 
297   const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
298   if (TokenRange.isInvalid())
299     return;
300 
301   StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
302                                             Result.Context->getLangOpts());
303   StringRef TranslatedSpelling = translate(Spelling);
304 
305   if (TranslatedSpelling.empty())
306     return;
307 
308   std::string FixSpelling = TranslatedSpelling.str();
309 
310   StringRef SourceRepresentation = "an alternative";
311   StringRef TargetRepresentation = "a traditional";
312   if (needEscaping(TranslatedSpelling)) {
313     SourceRepresentation = "a traditional";
314     TargetRepresentation = "an alternative";
315 
316     StringRef SpellingEx = Lexer::getSourceText(
317         CharSourceRange::getCharRange(
318             TokenRange.getBegin().getLocWithOffset(-1),
319             TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),
320         *Result.SourceManager, Result.Context->getLangOpts());
321     if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))
322       FixSpelling.insert(FixSpelling.begin(), ' ');
323     if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))
324       FixSpelling.push_back(' ');
325   }
326 
327   diag(
328       Loc,
329       "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
330       << Spelling << SourceRepresentation << TargetRepresentation
331       << TranslatedSpelling
332       << FixItHint::CreateReplacement(TokenRange, FixSpelling);
333 }
334 
335 } // namespace clang::tidy::readability
336