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

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp