clang-tools 22.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 clang::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 clang::ast_matchers::internal::Matcher<CXXMethodDecl>,
111 InnerMatcher) {
112 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
113}
114
115AST_POLYMORPHIC_MATCHER_P(
116 matchMemberName,
117 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),
118 std::string, MemberName) {
119 if (const auto *E = dyn_cast<MemberExpr>(&Node))
120 return E->getMemberDecl()->getName() == MemberName;
121
122 if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node))
123 return E->getMember().getAsString() == MemberName;
124
125 return false;
126}
127
128} // namespace
129
131
133 ClangTidyContext *Context)
134 : ClangTidyCheck(Name, Context),
135 ExcludedComparisonTypes(utils::options::parseStringList(
136 Options.get("ExcludedComparisonTypes", "::std::array"))) {}
137
139 Options.store(Opts, "ExcludedComparisonTypes",
140 utils::options::serializeStringList(ExcludedComparisonTypes));
141}
142
144 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
145 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
146 hasAnyName("size", "length"),
147 returns(qualType(isIntegralType(),
148 unless(booleanType()))))
149 .bind("size")),
150 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
151 hasName("empty"), returns(booleanType()))
152 .bind("empty")))
153 .bind("container")));
154
155 const auto ValidContainerNonTemplateType =
156 qualType(hasUnqualifiedDesugaredType(
157 recordType(hasDeclaration(ValidContainerRecord))));
158 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
159 anyOf(templateSpecializationType(
160 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
161 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
162
163 const auto ValidContainer = qualType(
164 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
165
166 const auto WrongUse =
167 anyOf(hasParent(binaryOperator(
168 isComparisonOperator(),
169 hasEitherOperand(anyOf(integerLiteral(equals(1)),
170 integerLiteral(equals(0)))))
171 .bind("SizeBinaryOp")),
172 usedInBooleanContext());
173
174 const auto NotInEmptyMethodOfContainer = unless(
175 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));
176
177 Finder->addMatcher(
178 cxxMemberCallExpr(
179 argumentCountIs(0),
180 on(expr(anyOf(hasType(ValidContainer),
181 hasType(pointsTo(ValidContainer)),
182 hasType(references(ValidContainer))))
183 .bind("MemberCallObject")),
184 callee(
185 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
186 WrongUse, NotInEmptyMethodOfContainer)
187 .bind("SizeCallExpr"),
188 this);
189
190 Finder->addMatcher(
191 callExpr(
192 argumentCountIs(0),
193 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
194 .with(
195 hasObjectExpression(
196 expr(anyOf(hasType(ValidContainer),
197 hasType(pointsTo(ValidContainer)),
198 hasType(references(ValidContainer))))
199 .bind("MemberCallObject")),
200 anyOf(matchMemberName("size"), matchMemberName("length")))
201 .bind("MemberExpr")),
202 WrongUse, NotInEmptyMethodOfContainer)
203 .bind("SizeCallExpr"),
204 this);
205
206 // Comparison to empty string or empty constructor.
207 const auto WrongComparend =
208 anyOf(stringLiteral(hasSize(0)),
209 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
210 cxxConstructExpr(argumentCountIs(0)),
211 cxxUnresolvedConstructExpr(argumentCountIs(0)));
212 // Match the object being compared.
213 const auto STLArg =
214 anyOf(unaryOperator(
215 hasOperatorName("*"),
216 hasUnaryOperand(
217 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
218 expr(hasType(ValidContainer)).bind("STLObject"));
219
220 const auto ExcludedComparisonTypesMatcher = qualType(anyOf(
221 hasDeclaration(
222 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))
223 .bind("excluded")),
224 hasCanonicalType(hasDeclaration(
225 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))
226 .bind("excluded")))));
227 const auto SameExcludedComparisonTypesMatcher =
228 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
229 hasCanonicalType(hasDeclaration(
230 cxxRecordDecl(equalsBoundNode("excluded"))))));
231
232 Finder->addMatcher(
233 binaryOperation(
234 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
235 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
236 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
237 NotInEmptyMethodOfContainer)
238 .bind("BinCmp"),
239 this);
240}
241
242void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
243 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
244 const auto *MemberCallObject =
245 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
246 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
247 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
248 const auto *BinCmpRewritten =
249 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
250 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
251 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
252 const auto *E =
253 MemberCallObject
254 ? MemberCallObject
255 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
256 FixItHint Hint;
257 std::string ReplacementText =
258 E->isImplicitCXXThis()
259 ? ""
260 : std::string(Lexer::getSourceText(
261 CharSourceRange::getTokenRange(E->getSourceRange()),
262 *Result.SourceManager, getLangOpts()));
263 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
264 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
265 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
266 ReplacementText = "(" + ReplacementText + ")";
267 }
268 if (OpCallExpr &&
269 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
270 // This can happen if the object is a smart pointer. Don't add anything
271 // because a '->' is already there (PR#51776), just call the method.
272 ReplacementText += "empty()";
273 } else if (E->isImplicitCXXThis()) {
274 ReplacementText += "empty()";
275 } else if (E->getType()->isPointerType())
276 ReplacementText += "->empty()";
277 else
278 ReplacementText += ".empty()";
279
280 if (BinCmp) {
281 if (BinCmp->getOperator() == OO_ExclaimEqual) {
282 ReplacementText = "!" + ReplacementText;
283 }
284 Hint =
285 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
286 } else if (BinCmpTempl) {
287 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
288 ReplacementText = "!" + ReplacementText;
289 }
290 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
291 ReplacementText);
292 } else if (BinCmpRewritten) {
293 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
294 ReplacementText = "!" + ReplacementText;
295 }
296 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
297 ReplacementText);
298 } else if (BinaryOp) { // Determine the correct transformation.
299 const auto *LiteralLHS =
300 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
301 const auto *LiteralRHS =
302 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
303 const bool ContainerIsLHS = !LiteralLHS;
304
305 uint64_t Value = 0;
306 if (LiteralLHS)
307 Value = LiteralLHS->getValue().getLimitedValue();
308 else if (LiteralRHS)
309 Value = LiteralRHS->getValue().getLimitedValue();
310 else
311 return;
312
313 bool Negation = false;
314 const auto OpCode = BinaryOp->getOpcode();
315
316 // Constant that is not handled.
317 if (Value > 1)
318 return;
319
320 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
321 OpCode == BinaryOperatorKind::BO_NE))
322 return;
323
324 // Always true/false, no warnings for that.
325 if (Value == 0) {
326 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
327 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
328 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
329 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
330 return;
331 }
332
333 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
334 if (Value == 1) {
335 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
336 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
337 return;
338 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
339 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
340 return;
341 }
342
343 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
344 // types
345 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
346 !ContainerIsLHS) ||
347 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
348 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
349 !ContainerIsLHS) ||
350 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
351 const Expr *Container = ContainerIsLHS
352 ? BinaryOp->getLHS()->IgnoreImpCasts()
353 : BinaryOp->getRHS()->IgnoreImpCasts();
354 if (Container->getType()
355 .getCanonicalType()
356 .getNonReferenceType()
357 ->isSignedIntegerType())
358 return;
359 }
360
361 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
362 Negation = true;
363
364 if ((OpCode == BinaryOperatorKind::BO_GT ||
365 OpCode == BinaryOperatorKind::BO_GE) &&
366 ContainerIsLHS)
367 Negation = true;
368
369 if ((OpCode == BinaryOperatorKind::BO_LT ||
370 OpCode == BinaryOperatorKind::BO_LE) &&
371 !ContainerIsLHS)
372 Negation = true;
373
374 if (Negation)
375 ReplacementText = "!" + ReplacementText;
376 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
377 ReplacementText);
378
379 } else {
380 // If there is a conversion above the size call to bool, it is safe to just
381 // replace size with empty.
382 if (const auto *UnaryOp =
383 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
384 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
385 ReplacementText);
386 else
387 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
388 "!" + ReplacementText);
389 }
390
391 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
392
393 if (WarnLoc.isValid()) {
394 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
395 "for emptiness instead of %0");
396 if (const auto *SizeMethod =
397 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
398 Diag << SizeMethod;
399 else if (const auto *DependentExpr =
400 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
401 "MemberExpr"))
402 Diag << DependentExpr->getMember();
403 else if (const auto *ME =
404 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 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
421 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
422 // The definition of the empty() method is the same for all implicit
423 // instantiations. In order to avoid duplicate or inconsistent warnings
424 // (depending on how deduplication is done), we use the same class name
425 // for all implicit instantiations of a template.
426 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
427 Container = CTS->getSpecializedTemplate();
428 }
429 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
430
431 diag(Empty->getLocation(), "method %0::empty() defined here",
432 DiagnosticIDs::Note)
433 << Container;
434}
435
436} // 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 > matchesAnyListedName(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