clang-tools 23.0.0git
ContainerSizeEmptyCheck.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//===----------------------------------------------------------------------===//
9#include "../utils/ASTUtils.h"
10#include "../utils/Matchers.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/StringRef.h"
16
17using namespace clang::ast_matchers;
18
20
21namespace {
22
23AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
24 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
25 CXXConstructExpr),
26 ast_matchers::internal::Matcher<Expr>, ArgMatcher,
27 ast_matchers::internal::Matcher<ParmVarDecl>,
28 ParamMatcher) {
29 ast_matchers::internal::BoundNodesTreeBuilder Result;
30 // The first argument of an overloaded member operator is the implicit object
31 // argument of the method which should not be matched against a parameter, so
32 // we skip over it here.
33 ast_matchers::internal::BoundNodesTreeBuilder Matches;
34 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
35 .matches(Node, Finder, &Matches)
36 ? 1
37 : 0;
38 int ParamIndex = 0;
39 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
40 ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder);
41 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
42 &ArgMatches)) {
43 ast_matchers::internal::BoundNodesTreeBuilder ParamMatches(ArgMatches);
44 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
45 hasParameter(ParamIndex, ParamMatcher)))),
46 callExpr(callee(functionDecl(
47 hasParameter(ParamIndex, ParamMatcher))))))
48 .matches(Node, Finder, &ParamMatches)) {
49 Result.addMatch(ParamMatches);
50 *Builder = std::move(Result);
51 return true;
52 }
53 }
54 ++ParamIndex;
55 }
56 return false;
57}
58
59AST_MATCHER(Expr, usedInBooleanContext) {
60 const char *ExprName = "__booleanContextExpr";
61 auto Result =
62 expr(expr().bind(ExprName),
63 anyOf(hasParent(
64 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
65 hasParent(cxxConstructorDecl(
66 hasAnyConstructorInitializer(cxxCtorInitializer(
67 withInitializer(expr(equalsBoundNode(ExprName))),
68 forField(hasType(booleanType())))))),
69 hasParent(stmt(anyOf(
70 explicitCastExpr(hasDestinationType(booleanType())),
71 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
72 conditionalOperator)
73 .with(hasCondition(expr(equalsBoundNode(ExprName)))),
74 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
75 parenExpr(hasParent(
76 explicitCastExpr(hasDestinationType(booleanType())))),
77 returnStmt(forFunction(returns(booleanType()))),
78 cxxUnresolvedConstructExpr(hasType(booleanType())),
79 invocation(hasAnyArgumentWithParam(
80 expr(equalsBoundNode(ExprName)),
81 parmVarDecl(hasType(booleanType())))),
82 binaryOperator(hasAnyOperatorName("&&", "||")),
83 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
84 .matches(Node, Finder, Builder);
85 Builder->removeBindings(
86 [ExprName](const ast_matchers::internal::BoundNodesMap &Nodes) {
87 return Nodes.getNode(ExprName).getNodeKind().isNone();
88 });
89 return Result;
90}
91
92AST_MATCHER(QualType, isIntegralType) {
93 return Node->isIntegralType(Finder->getASTContext());
94}
95
96AST_MATCHER_P(UserDefinedLiteral, hasLiteral,
97 ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
98 const UserDefinedLiteral::LiteralOperatorKind LOK =
99 Node.getLiteralOperatorKind();
100 if (LOK == UserDefinedLiteral::LOK_Template ||
101 LOK == UserDefinedLiteral::LOK_Raw)
102 return false;
103
104 if (const Expr *CookedLiteral = Node.getCookedLiteral())
105 return InnerMatcher.matches(*CookedLiteral, Finder, Builder);
106 return false;
107}
108
109AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
110 ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
111 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
112}
113
114AST_POLYMORPHIC_MATCHER_P(
115 matchMemberName,
116 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),
117 std::string, MemberName) {
118 if (const auto *E = dyn_cast<MemberExpr>(&Node)) {
119 const IdentifierInfo *II = E->getMemberDecl()->getIdentifier();
120 return II && II->getName() == MemberName;
121 }
122
123 if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node)) {
124 const IdentifierInfo *II = E->getMember().getAsIdentifierInfo();
125 return II && II->getName() == MemberName;
126 }
127
128 return false;
129}
130
131} // namespace
132
134
136 ClangTidyContext *Context)
137 : ClangTidyCheck(Name, Context),
138 ExcludedComparisonTypes(utils::options::parseStringList(
139 Options.get("ExcludedComparisonTypes", "::std::array"))) {}
140
142 Options.store(Opts, "ExcludedComparisonTypes",
143 utils::options::serializeStringList(ExcludedComparisonTypes));
144}
145
147 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(namedDecl(
148 has(cxxMethodDecl(
149 isConst(), parameterCountIs(0), isPublic(),
150 hasAnyName("size", "length"),
151 returns(qualType(isIntegralType(), unless(booleanType()))))
152 .bind("size")),
153 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
154 hasName("empty"), returns(booleanType()))
155 .bind("empty")))));
156
157 const auto ValidContainerNonTemplateType =
158 qualType(hasUnqualifiedDesugaredType(
159 recordType(hasDeclaration(ValidContainerRecord))));
160 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
161 anyOf(templateSpecializationType(
162 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
163 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
164
165 const auto ValidContainer = qualType(
166 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
167
168 const auto WrongUse =
169 anyOf(hasParent(binaryOperator(
170 isComparisonOperator(),
171 hasEitherOperand(anyOf(integerLiteral(equals(1)),
172 integerLiteral(equals(0)))))
173 .bind("SizeBinaryOp")),
174 usedInBooleanContext());
175
176 const auto NotInEmptyMethodOfContainer = unless(
177 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));
178
179 Finder->addMatcher(
180 cxxMemberCallExpr(
181 argumentCountIs(0),
182 on(expr(anyOf(hasType(ValidContainer),
183 hasType(pointsTo(ValidContainer)),
184 hasType(references(ValidContainer))))
185 .bind("MemberCallObject")),
186 callee(
187 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
188 WrongUse, NotInEmptyMethodOfContainer)
189 .bind("SizeCallExpr"),
190 this);
191
192 Finder->addMatcher(
193 callExpr(
194 argumentCountIs(0),
195 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
196 .with(
197 hasObjectExpression(
198 expr(anyOf(hasType(ValidContainer),
199 hasType(pointsTo(ValidContainer)),
200 hasType(references(ValidContainer))))
201 .bind("MemberCallObject")),
202 anyOf(matchMemberName("size"), matchMemberName("length")))
203 .bind("MemberExpr")),
204 WrongUse, NotInEmptyMethodOfContainer)
205 .bind("SizeCallExpr"),
206 this);
207
208 // Comparison to empty string or empty constructor.
209 const auto WrongComparend =
210 anyOf(stringLiteral(hasSize(0)),
211 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
212 cxxConstructExpr(argumentCountIs(0)),
213 cxxUnresolvedConstructExpr(argumentCountIs(0)));
214 // Match the object being compared.
215 const auto STLArg =
216 anyOf(unaryOperator(
217 hasOperatorName("*"),
218 hasUnaryOperand(
219 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
220 expr(hasType(ValidContainer)).bind("STLObject"));
221
222 const auto ExcludedComparisonTypesMatcher = qualType(
223 anyOf(hasDeclaration(cxxRecordDecl(matchers::matchesAnyListedRegexName(
224 ExcludedComparisonTypes))
225 .bind("excluded")),
226 hasCanonicalType(hasDeclaration(
228 ExcludedComparisonTypes))
229 .bind("excluded")))));
230 const auto SameExcludedComparisonTypesMatcher =
231 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
232 hasCanonicalType(hasDeclaration(
233 cxxRecordDecl(equalsBoundNode("excluded"))))));
234
235 Finder->addMatcher(
236 binaryOperation(
237 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
238 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
239 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
240 NotInEmptyMethodOfContainer)
241 .bind("BinCmp"),
242 this);
243}
244
245void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
246 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
247 const auto *MemberCallObject =
248 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
249 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
250 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
251 const auto *BinCmpRewritten =
252 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
253 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
254 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
255 const auto *E =
256 MemberCallObject
257 ? MemberCallObject
258 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
259 FixItHint Hint;
260 std::string ReplacementText =
261 E->isImplicitCXXThis()
262 ? ""
263 : std::string(Lexer::getSourceText(
264 CharSourceRange::getTokenRange(E->getSourceRange()),
265 *Result.SourceManager, getLangOpts()));
266 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
267 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
268 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
269 ReplacementText = "(" + ReplacementText + ")";
270 }
271 if (OpCallExpr &&
272 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
273 // This can happen if the object is a smart pointer. Don't add anything
274 // because a '->' is already there (PR#51776), just call the method.
275 ReplacementText += "empty()";
276 } else if (E->isImplicitCXXThis()) {
277 ReplacementText += "empty()";
278 } else if (E->getType()->isPointerType()) {
279 ReplacementText += "->empty()";
280 } else {
281 ReplacementText += ".empty()";
282 }
283
284 if (BinCmp) {
285 if (BinCmp->getOperator() == OO_ExclaimEqual)
286 ReplacementText = "!" + ReplacementText;
287 Hint =
288 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
289 } else if (BinCmpTempl) {
290 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
291 ReplacementText = "!" + ReplacementText;
292 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
293 ReplacementText);
294 } else if (BinCmpRewritten) {
295 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
296 ReplacementText = "!" + ReplacementText;
297 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
298 ReplacementText);
299 } else if (BinaryOp) { // Determine the correct transformation.
300 const auto *LiteralLHS =
301 dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
302 const auto *LiteralRHS =
303 dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
304 const bool ContainerIsLHS = !LiteralLHS;
305
306 uint64_t Value = 0;
307 if (LiteralLHS)
308 Value = LiteralLHS->getValue().getLimitedValue();
309 else if (LiteralRHS)
310 Value = LiteralRHS->getValue().getLimitedValue();
311 else
312 return;
313
314 bool Negation = false;
315 const auto OpCode = BinaryOp->getOpcode();
316
317 // Constant that is not handled.
318 if (Value > 1)
319 return;
320
321 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
322 OpCode == BinaryOperatorKind::BO_NE))
323 return;
324
325 // Always true/false, no warnings for that.
326 if (Value == 0) {
327 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
328 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
329 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
330 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
331 return;
332 }
333
334 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
335 if (Value == 1) {
336 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
337 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
338 return;
339 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
340 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
341 return;
342 }
343
344 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
345 // types
346 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
347 !ContainerIsLHS) ||
348 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
349 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
350 !ContainerIsLHS) ||
351 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
352 const Expr *Container = ContainerIsLHS
353 ? BinaryOp->getLHS()->IgnoreImpCasts()
354 : BinaryOp->getRHS()->IgnoreImpCasts();
355 if (Container->getType()
356 .getCanonicalType()
357 .getNonReferenceType()
358 ->isSignedIntegerType())
359 return;
360 }
361
362 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
363 Negation = true;
364
365 if ((OpCode == BinaryOperatorKind::BO_GT ||
366 OpCode == BinaryOperatorKind::BO_GE) &&
367 ContainerIsLHS)
368 Negation = true;
369
370 if ((OpCode == BinaryOperatorKind::BO_LT ||
371 OpCode == BinaryOperatorKind::BO_LE) &&
372 !ContainerIsLHS)
373 Negation = true;
374
375 if (Negation)
376 ReplacementText = "!" + ReplacementText;
377 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
378 ReplacementText);
379
380 } else {
381 // If there is a conversion above the size call to bool, it is safe to just
382 // replace size with empty.
383 if (const auto *UnaryOp =
384 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
385 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
386 ReplacementText);
387 else
388 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
389 "!" + ReplacementText);
390 }
391
392 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
393
394 if (WarnLoc.isValid()) {
395 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
396 "for emptiness instead of %0");
397 if (const auto *SizeMethod =
398 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
399 Diag << SizeMethod;
400 else if (const auto *DependentExpr =
401 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
402 "MemberExpr"))
403 Diag << DependentExpr->getMember();
404 else if (const auto *ME = Result.Nodes.getNodeAs<MemberExpr>("MemberExpr"))
405 Diag << ME->getMemberNameInfo().getName();
406 else
407 Diag << "unknown method";
408 Diag << Hint;
409 } else {
410 WarnLoc = BinCmpTempl
411 ? BinCmpTempl->getBeginLoc()
412 : (BinCmp ? BinCmp->getBeginLoc()
413 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
414 : SourceLocation{}));
415 diag(WarnLoc, "the 'empty' method should be used to check "
416 "for emptiness instead of comparing to an empty object")
417 << Hint;
418 }
419}
420
421} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
bool isBinaryOrTernary(const Expr *E)
Definition ASTUtils.cpp:25
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
bool isBinaryOrTernary(const Expr *E)
Definition ASTUtils.cpp:25
llvm::StringMap< ClangTidyValue > OptionMap