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