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