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 "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
14 #include "llvm/ADT/StringRef.h"
15
16 using namespace clang::ast_matchers;
17
18 namespace clang {
19 namespace ast_matchers {
AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,AST_POLYMORPHIC_SUPPORTED_TYPES (CallExpr,CXXConstructExpr),internal::Matcher<Expr>,ArgMatcher,internal::Matcher<ParmVarDecl>,ParamMatcher)20 AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
21 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
22 CXXConstructExpr),
23 internal::Matcher<Expr>, ArgMatcher,
24 internal::Matcher<ParmVarDecl>, ParamMatcher) {
25 BoundNodesTreeBuilder Result;
26 // The first argument of an overloaded member operator is the implicit object
27 // argument of the method which should not be matched against a parameter, so
28 // we skip over it here.
29 BoundNodesTreeBuilder Matches;
30 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
31 .matches(Node, Finder, &Matches)
32 ? 1
33 : 0;
34 int ParamIndex = 0;
35 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
36 BoundNodesTreeBuilder ArgMatches(*Builder);
37 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
38 &ArgMatches)) {
39 BoundNodesTreeBuilder ParamMatches(ArgMatches);
40 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
41 hasParameter(ParamIndex, ParamMatcher)))),
42 callExpr(callee(functionDecl(
43 hasParameter(ParamIndex, ParamMatcher))))))
44 .matches(Node, Finder, &ParamMatches)) {
45 Result.addMatch(ParamMatches);
46 *Builder = std::move(Result);
47 return true;
48 }
49 }
50 ++ParamIndex;
51 }
52 return false;
53 }
54
AST_MATCHER(Expr,usedInBooleanContext)55 AST_MATCHER(Expr, usedInBooleanContext) {
56 const char *ExprName = "__booleanContextExpr";
57 auto Result =
58 expr(expr().bind(ExprName),
59 anyOf(hasParent(
60 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
61 hasParent(cxxConstructorDecl(
62 hasAnyConstructorInitializer(cxxCtorInitializer(
63 withInitializer(expr(equalsBoundNode(ExprName))),
64 forField(hasType(booleanType())))))),
65 hasParent(stmt(anyOf(
66 explicitCastExpr(hasDestinationType(booleanType())),
67 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
68 conditionalOperator)
69 .with(hasCondition(expr(equalsBoundNode(ExprName)))),
70 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
71 parenExpr(hasParent(
72 explicitCastExpr(hasDestinationType(booleanType())))),
73 returnStmt(forFunction(returns(booleanType()))),
74 cxxUnresolvedConstructExpr(hasType(booleanType())),
75 invocation(hasAnyArgumentWithParam(
76 expr(equalsBoundNode(ExprName)),
77 parmVarDecl(hasType(booleanType())))),
78 binaryOperator(hasAnyOperatorName("&&", "||")),
79 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
80 .matches(Node, Finder, Builder);
81 Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) {
82 return Nodes.getNode(ExprName).getNodeKind().isNone();
83 });
84 return Result;
85 }
AST_MATCHER(CXXConstructExpr,isDefaultConstruction)86 AST_MATCHER(CXXConstructExpr, isDefaultConstruction) {
87 return Node.getConstructor()->isDefaultConstructor();
88 }
89 } // namespace ast_matchers
90 namespace tidy {
91 namespace readability {
92
93 using utils::isBinaryOrTernary;
94
ContainerSizeEmptyCheck(StringRef Name,ClangTidyContext * Context)95 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
96 ClangTidyContext *Context)
97 : ClangTidyCheck(Name, Context) {}
98
registerMatchers(MatchFinder * Finder)99 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
100 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
101 namedDecl(
102 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
103 hasName("size"),
104 returns(qualType(isInteger(), unless(booleanType()),
105 unless(elaboratedType()))))
106 .bind("size")),
107 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
108 hasName("empty"), returns(booleanType()))
109 .bind("empty")))
110 .bind("container")));
111
112 const auto ValidContainerNonTemplateType =
113 qualType(hasUnqualifiedDesugaredType(
114 recordType(hasDeclaration(ValidContainerRecord))));
115 const auto ValidContainerTemplateType =
116 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
117 hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
118
119 const auto ValidContainer = qualType(
120 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
121
122 const auto WrongUse =
123 anyOf(hasParent(binaryOperator(
124 isComparisonOperator(),
125 hasEitherOperand(anyOf(integerLiteral(equals(1)),
126 integerLiteral(equals(0)))))
127 .bind("SizeBinaryOp")),
128 usedInBooleanContext());
129
130 Finder->addMatcher(
131 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
132 hasType(pointsTo(ValidContainer)),
133 hasType(references(ValidContainer))))
134 .bind("MemberCallObject")),
135 callee(cxxMethodDecl(hasName("size"))), WrongUse,
136 unless(hasAncestor(cxxMethodDecl(
137 ofClass(equalsBoundNode("container"))))))
138 .bind("SizeCallExpr"),
139 this);
140
141 Finder->addMatcher(
142 callExpr(has(cxxDependentScopeMemberExpr(
143 hasObjectExpression(
144 expr(anyOf(hasType(ValidContainer),
145 hasType(pointsTo(ValidContainer)),
146 hasType(references(ValidContainer))))
147 .bind("MemberCallObject")),
148 hasMemberName("size"))),
149 WrongUse,
150 unless(hasAncestor(
151 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
152 .bind("SizeCallExpr"),
153 this);
154
155 // Comparison to empty string or empty constructor.
156 const auto WrongComparend = anyOf(
157 stringLiteral(hasSize(0)), cxxConstructExpr(isDefaultConstruction()),
158 cxxUnresolvedConstructExpr(argumentCountIs(0)));
159 // Match the object being compared.
160 const auto STLArg =
161 anyOf(unaryOperator(
162 hasOperatorName("*"),
163 hasUnaryOperand(
164 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
165 expr(hasType(ValidContainer)).bind("STLObject"));
166 Finder->addMatcher(
167 binaryOperation(hasAnyOperatorName("==", "!="),
168 hasOperands(WrongComparend,
169 STLArg),
170 unless(hasAncestor(cxxMethodDecl(
171 ofClass(equalsBoundNode("container"))))))
172 .bind("BinCmp"),
173 this);
174 }
175
check(const MatchFinder::MatchResult & Result)176 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
177 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
178 const auto *MemberCallObject =
179 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
180 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
181 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
182 const auto *BinCmpRewritten =
183 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
184 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
185 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
186 const auto *E =
187 MemberCallObject
188 ? MemberCallObject
189 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
190 FixItHint Hint;
191 std::string ReplacementText = std::string(
192 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
193 *Result.SourceManager, getLangOpts()));
194 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
195 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
196 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
197 ReplacementText = "(" + ReplacementText + ")";
198 }
199 if (OpCallExpr &&
200 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
201 // This can happen if the object is a smart pointer. Don't add anything
202 // because a '->' is already there (PR#51776), just call the method.
203 ReplacementText += "empty()";
204 } else if (E->getType()->isPointerType())
205 ReplacementText += "->empty()";
206 else
207 ReplacementText += ".empty()";
208
209 if (BinCmp) {
210 if (BinCmp->getOperator() == OO_ExclaimEqual) {
211 ReplacementText = "!" + ReplacementText;
212 }
213 Hint =
214 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
215 } else if (BinCmpTempl) {
216 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
217 ReplacementText = "!" + ReplacementText;
218 }
219 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
220 ReplacementText);
221 } else if (BinCmpRewritten) {
222 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
223 ReplacementText = "!" + ReplacementText;
224 }
225 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
226 ReplacementText);
227 } else if (BinaryOp) { // Determine the correct transformation.
228 const auto *LiteralLHS =
229 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
230 const auto *LiteralRHS =
231 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
232 const bool ContainerIsLHS = !LiteralLHS;
233
234 uint64_t Value = 0;
235 if (LiteralLHS)
236 Value = LiteralLHS->getValue().getLimitedValue();
237 else if (LiteralRHS)
238 Value = LiteralRHS->getValue().getLimitedValue();
239 else
240 return;
241
242 bool Negation = false;
243 const auto OpCode = BinaryOp->getOpcode();
244
245 // Constant that is not handled.
246 if (Value > 1)
247 return;
248
249 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
250 OpCode == BinaryOperatorKind::BO_NE))
251 return;
252
253 // Always true, no warnings for that.
254 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
255 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
256 return;
257
258 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
259 if (Value == 1) {
260 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
261 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
262 return;
263 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
264 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
265 return;
266 }
267
268 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
269 Negation = true;
270 if ((OpCode == BinaryOperatorKind::BO_GT ||
271 OpCode == BinaryOperatorKind::BO_GE) &&
272 ContainerIsLHS)
273 Negation = true;
274 if ((OpCode == BinaryOperatorKind::BO_LT ||
275 OpCode == BinaryOperatorKind::BO_LE) &&
276 !ContainerIsLHS)
277 Negation = true;
278
279 if (Negation)
280 ReplacementText = "!" + ReplacementText;
281 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
282 ReplacementText);
283
284 } else {
285 // If there is a conversion above the size call to bool, it is safe to just
286 // replace size with empty.
287 if (const auto *UnaryOp =
288 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
289 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
290 ReplacementText);
291 else
292 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
293 "!" + ReplacementText);
294 }
295
296 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
297
298 if (WarnLoc.isValid()) {
299 diag(WarnLoc, "the 'empty' method should be used to check "
300 "for emptiness instead of 'size'")
301 << Hint;
302 } else {
303 WarnLoc = BinCmpTempl
304 ? BinCmpTempl->getBeginLoc()
305 : (BinCmp ? BinCmp->getBeginLoc()
306 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
307 : SourceLocation{}));
308 diag(WarnLoc, "the 'empty' method should be used to check "
309 "for emptiness instead of comparing to an empty object")
310 << Hint;
311 }
312
313 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
314 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
315 // The definition of the empty() method is the same for all implicit
316 // instantiations. In order to avoid duplicate or inconsistent warnings
317 // (depending on how deduplication is done), we use the same class name
318 // for all implicit instantiations of a template.
319 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
320 Container = CTS->getSpecializedTemplate();
321 }
322 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
323
324 diag(Empty->getLocation(), "method %0::empty() defined here",
325 DiagnosticIDs::Note)
326 << Container;
327 }
328
329 } // namespace readability
330 } // namespace tidy
331 } // namespace clang
332