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(
221 anyOf(hasDeclaration(cxxRecordDecl(matchers::matchesAnyListedRegexName(
222 ExcludedComparisonTypes))
223 .bind("excluded")),
224 hasCanonicalType(hasDeclaration(
226 ExcludedComparisonTypes))
227 .bind("excluded")))));
228 const auto SameExcludedComparisonTypesMatcher =
229 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
230 hasCanonicalType(hasDeclaration(
231 cxxRecordDecl(equalsBoundNode("excluded"))))));
232
233 Finder->addMatcher(
234 binaryOperation(
235 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
236 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
237 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
238 NotInEmptyMethodOfContainer)
239 .bind("BinCmp"),
240 this);
241}
242
243void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
244 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
245 const auto *MemberCallObject =
246 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
247 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
248 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
249 const auto *BinCmpRewritten =
250 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
251 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
252 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
253 const auto *E =
254 MemberCallObject
255 ? MemberCallObject
256 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
257 FixItHint Hint;
258 std::string ReplacementText =
259 E->isImplicitCXXThis()
260 ? ""
261 : std::string(Lexer::getSourceText(
262 CharSourceRange::getTokenRange(E->getSourceRange()),
263 *Result.SourceManager, getLangOpts()));
264 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
265 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
266 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
267 ReplacementText = "(" + ReplacementText + ")";
268 }
269 if (OpCallExpr &&
270 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
271 // This can happen if the object is a smart pointer. Don't add anything
272 // because a '->' is already there (PR#51776), just call the method.
273 ReplacementText += "empty()";
274 } else if (E->isImplicitCXXThis()) {
275 ReplacementText += "empty()";
276 } else if (E->getType()->isPointerType())
277 ReplacementText += "->empty()";
278 else
279 ReplacementText += ".empty()";
280
281 if (BinCmp) {
282 if (BinCmp->getOperator() == OO_ExclaimEqual)
283 ReplacementText = "!" + ReplacementText;
284 Hint =
285 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
286 } else if (BinCmpTempl) {
287 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
288 ReplacementText = "!" + ReplacementText;
289 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
290 ReplacementText);
291 } else if (BinCmpRewritten) {
292 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
293 ReplacementText = "!" + ReplacementText;
294 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
295 ReplacementText);
296 } else if (BinaryOp) { // Determine the correct transformation.
297 const auto *LiteralLHS =
298 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
299 const auto *LiteralRHS =
300 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
301 const bool ContainerIsLHS = !LiteralLHS;
302
303 uint64_t Value = 0;
304 if (LiteralLHS)
305 Value = LiteralLHS->getValue().getLimitedValue();
306 else if (LiteralRHS)
307 Value = LiteralRHS->getValue().getLimitedValue();
308 else
309 return;
310
311 bool Negation = false;
312 const auto OpCode = BinaryOp->getOpcode();
313
314 // Constant that is not handled.
315 if (Value > 1)
316 return;
317
318 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
319 OpCode == BinaryOperatorKind::BO_NE))
320 return;
321
322 // Always true/false, no warnings for that.
323 if (Value == 0) {
324 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
325 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
326 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
327 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
328 return;
329 }
330
331 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
332 if (Value == 1) {
333 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
334 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
335 return;
336 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
337 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
338 return;
339 }
340
341 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
342 // types
343 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
344 !ContainerIsLHS) ||
345 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
346 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
347 !ContainerIsLHS) ||
348 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
349 const Expr *Container = ContainerIsLHS
350 ? BinaryOp->getLHS()->IgnoreImpCasts()
351 : BinaryOp->getRHS()->IgnoreImpCasts();
352 if (Container->getType()
353 .getCanonicalType()
354 .getNonReferenceType()
355 ->isSignedIntegerType())
356 return;
357 }
358
359 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
360 Negation = true;
361
362 if ((OpCode == BinaryOperatorKind::BO_GT ||
363 OpCode == BinaryOperatorKind::BO_GE) &&
364 ContainerIsLHS)
365 Negation = true;
366
367 if ((OpCode == BinaryOperatorKind::BO_LT ||
368 OpCode == BinaryOperatorKind::BO_LE) &&
369 !ContainerIsLHS)
370 Negation = true;
371
372 if (Negation)
373 ReplacementText = "!" + ReplacementText;
374 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
375 ReplacementText);
376
377 } else {
378 // If there is a conversion above the size call to bool, it is safe to just
379 // replace size with empty.
380 if (const auto *UnaryOp =
381 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
382 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
383 ReplacementText);
384 else
385 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
386 "!" + ReplacementText);
387 }
388
389 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
390
391 if (WarnLoc.isValid()) {
392 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
393 "for emptiness instead of %0");
394 if (const auto *SizeMethod =
395 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
396 Diag << SizeMethod;
397 else if (const auto *DependentExpr =
398 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
399 "MemberExpr"))
400 Diag << DependentExpr->getMember();
401 else if (const auto *ME = Result.Nodes.getNodeAs<MemberExpr>("MemberExpr"))
402 Diag << ME->getMemberNameInfo().getName();
403 else
404 Diag << "unknown method";
405 Diag << Hint;
406 } else {
407 WarnLoc = BinCmpTempl
408 ? BinCmpTempl->getBeginLoc()
409 : (BinCmp ? BinCmp->getBeginLoc()
410 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
411 : SourceLocation{}));
412 diag(WarnLoc, "the 'empty' method should be used to check "
413 "for emptiness instead of comparing to an empty object")
414 << Hint;
415 }
416
417 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
418 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
419 // The definition of the empty() method is the same for all implicit
420 // instantiations. In order to avoid duplicate or inconsistent warnings
421 // (depending on how deduplication is done), we use the same class name
422 // for all implicit instantiations of a template.
423 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
424 Container = CTS->getSpecializedTemplate();
425 }
426 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
427
428 diag(Empty->getLocation(), "method %0::empty() defined here",
429 DiagnosticIDs::Note)
430 << Container;
431}
432
433} // 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