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