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 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 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 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 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 for (const auto &[traditional, alternative] : Operators) {
140 if (!getRepresentation(Config, traditional, alternative).empty())
141 return true;
142 }
143 return false;
144}
145
147 StringRef Name, ClangTidyContext *Context)
148 : ClangTidyCheck(Name, Context),
149 BinaryOperators(
150 utils::options::parseStringList(Options.get("BinaryOperators", ""))),
151 OverloadedOperators(utils::options::parseStringList(
152 Options.get("OverloadedOperators", ""))) {
153 llvm::erase_if(BinaryOperators, isNotOperatorStr);
154 llvm::erase_if(OverloadedOperators, isNotOperatorStr);
155}
156
159 Options.store(Opts, "BinaryOperators",
160 utils::options::serializeStringList(BinaryOperators));
161 Options.store(Opts, "OverloadedOperators",
162 utils::options::serializeStringList(OverloadedOperators));
163}
164
165std::optional<TraversalKind>
167 return TK_IgnoreUnlessSpelledInSource;
168}
169
171 const LangOptions &LangOpts) const {
172 return LangOpts.CPlusPlus;
173}
174
175void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
176 MatchFinder *Finder) {
177 if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
178 return;
179
180 Finder->addMatcher(
181 binaryOperator(
182 unless(isExpansionInSystemHeader()),
183 anyOf(hasInvalidBinaryOperatorRepresentation(
184 BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),
185 hasInvalidBinaryOperatorRepresentation(
186 BO_LOr, getRepresentation(BinaryOperators, "||", "or")),
187 hasInvalidBinaryOperatorRepresentation(
188 BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),
189 hasInvalidBinaryOperatorRepresentation(
190 BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),
191 hasInvalidBinaryOperatorRepresentation(
192 BO_And, getRepresentation(BinaryOperators, "&", "bitand")),
193 hasInvalidBinaryOperatorRepresentation(
194 BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),
195 hasInvalidBinaryOperatorRepresentation(
196 BO_AndAssign,
197 getRepresentation(BinaryOperators, "&=", "and_eq")),
198 hasInvalidBinaryOperatorRepresentation(
199 BO_OrAssign,
200 getRepresentation(BinaryOperators, "|=", "or_eq")),
201 hasInvalidBinaryOperatorRepresentation(
202 BO_XorAssign,
203 getRepresentation(BinaryOperators, "^=", "xor_eq"))))
204 .bind("binary_op"),
205 this);
206}
207
208void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
209 MatchFinder *Finder) {
210 if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
211 return;
212
213 Finder->addMatcher(
214 unaryOperator(
215 unless(isExpansionInSystemHeader()),
216 anyOf(hasInvalidUnaryOperatorRepresentation(
217 UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
218 hasInvalidUnaryOperatorRepresentation(
219 UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
220 .bind("unary_op"),
221 this);
222}
223
224void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
225 MatchFinder *Finder) {
226 if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
227 !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
228 return;
229
230 Finder->addMatcher(
231 cxxOperatorCallExpr(
232 unless(isExpansionInSystemHeader()),
233 anyOf(
234 hasInvalidOverloadedOperatorRepresentation(
235 OO_AmpAmp,
236 getRepresentation(OverloadedOperators, "&&", "and")),
237 hasInvalidOverloadedOperatorRepresentation(
238 OO_PipePipe,
239 getRepresentation(OverloadedOperators, "||", "or")),
240 hasInvalidOverloadedOperatorRepresentation(
241 OO_Exclaim,
242 getRepresentation(OverloadedOperators, "!", "not")),
243 hasInvalidOverloadedOperatorRepresentation(
244 OO_ExclaimEqual,
245 getRepresentation(OverloadedOperators, "!=", "not_eq")),
246 hasInvalidOverloadedOperatorRepresentation(
247 OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
248 hasInvalidOverloadedOperatorRepresentation(
249 OO_Amp,
250 getRepresentation(OverloadedOperators, "&", "bitand")),
251 hasInvalidOverloadedOperatorRepresentation(
252 OO_Pipe,
253 getRepresentation(OverloadedOperators, "|", "bitor")),
254 hasInvalidOverloadedOperatorRepresentation(
255 OO_AmpEqual,
256 getRepresentation(OverloadedOperators, "&=", "and_eq")),
257 hasInvalidOverloadedOperatorRepresentation(
258 OO_PipeEqual,
259 getRepresentation(OverloadedOperators, "|=", "or_eq")),
260 hasInvalidOverloadedOperatorRepresentation(
261 OO_CaretEqual,
262 getRepresentation(OverloadedOperators, "^=", "xor_eq")),
263 hasInvalidOverloadedOperatorRepresentation(
264 OO_Tilde,
265 getRepresentation(OverloadedOperators, "~", "compl"))))
266 .bind("overloaded_op"),
267 this);
268}
269
271 registerBinaryOperatorMatcher(Finder);
272 registerUnaryOperatorMatcher(Finder);
273 registerOverloadedOperatorMatcher(Finder);
274}
275
277 const MatchFinder::MatchResult &Result) {
278
279 SourceLocation Loc;
280
281 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
282 Loc = Op->getOperatorLoc();
283 else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))
284 Loc = Op->getOperatorLoc();
285 else if (const auto *Op =
286 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))
287 Loc = Op->getOperatorLoc();
288
289 if (Loc.isInvalid())
290 return;
291
292 Loc = Result.SourceManager->getSpellingLoc(Loc);
293 if (Loc.isInvalid() || Loc.isMacroID())
294 return;
295
296 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
297 if (TokenRange.isInvalid())
298 return;
299
300 StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
301 Result.Context->getLangOpts());
302 StringRef TranslatedSpelling = translate(Spelling);
303
304 if (TranslatedSpelling.empty())
305 return;
306
307 std::string FixSpelling = TranslatedSpelling.str();
308
309 StringRef SourceRepresentation = "an alternative";
310 StringRef TargetRepresentation = "a traditional";
311 if (needEscaping(TranslatedSpelling)) {
312 SourceRepresentation = "a traditional";
313 TargetRepresentation = "an alternative";
314
315 StringRef SpellingEx = Lexer::getSourceText(
316 CharSourceRange::getCharRange(
317 TokenRange.getBegin().getLocWithOffset(-1),
318 TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),
319 *Result.SourceManager, Result.Context->getLangOpts());
320 if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))
321 FixSpelling.insert(FixSpelling.begin(), ' ');
322 if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))
323 FixSpelling.push_back(' ');
324 }
325
326 diag(
327 Loc,
328 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
329 << Spelling << SourceRepresentation << TargetRepresentation
330 << TranslatedSpelling
331 << FixItHint::CreateReplacement(TokenRange, FixSpelling);
332}
333
334} // 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