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