Mercurial > hg > CbC > CbC_llvm
comparison clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @ 150:1d019706d866
LLVM10
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 15:10:13 +0900 |
parents | |
children | 0572611fdcc8 |
comparison
equal
deleted
inserted
replaced
147:c2174574ed3a | 150:1d019706d866 |
---|---|
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 tidy { | |
20 namespace readability { | |
21 | |
22 using utils::IsBinaryOrTernary; | |
23 | |
24 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, | |
25 ClangTidyContext *Context) | |
26 : ClangTidyCheck(Name, Context) {} | |
27 | |
28 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { | |
29 // Only register the matchers for C++; the functionality currently does not | |
30 // provide any benefit to other languages, despite being benign. | |
31 if (!getLangOpts().CPlusPlus) | |
32 return; | |
33 | |
34 const auto ValidContainer = qualType(hasUnqualifiedDesugaredType( | |
35 recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom( | |
36 namedDecl( | |
37 has(cxxMethodDecl( | |
38 isConst(), parameterCountIs(0), isPublic(), | |
39 hasName("size"), | |
40 returns(qualType(isInteger(), unless(booleanType())))) | |
41 .bind("size")), | |
42 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), | |
43 hasName("empty"), returns(booleanType())) | |
44 .bind("empty"))) | |
45 .bind("container"))))))); | |
46 | |
47 const auto WrongUse = anyOf( | |
48 hasParent(binaryOperator( | |
49 matchers::isComparisonOperator(), | |
50 hasEitherOperand(ignoringImpCasts(anyOf( | |
51 integerLiteral(equals(1)), integerLiteral(equals(0)))))) | |
52 .bind("SizeBinaryOp")), | |
53 hasParent(implicitCastExpr( | |
54 hasImplicitDestinationType(booleanType()), | |
55 anyOf( | |
56 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")), | |
57 anything()))), | |
58 hasParent(explicitCastExpr(hasDestinationType(booleanType())))); | |
59 | |
60 Finder->addMatcher( | |
61 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), | |
62 hasType(pointsTo(ValidContainer)), | |
63 hasType(references(ValidContainer))))), | |
64 callee(cxxMethodDecl(hasName("size"))), WrongUse, | |
65 unless(hasAncestor(cxxMethodDecl( | |
66 ofClass(equalsBoundNode("container")))))) | |
67 .bind("SizeCallExpr"), | |
68 this); | |
69 | |
70 // Empty constructor matcher. | |
71 const auto DefaultConstructor = cxxConstructExpr( | |
72 hasDeclaration(cxxConstructorDecl(isDefaultConstructor()))); | |
73 // Comparison to empty string or empty constructor. | |
74 const auto WrongComparend = anyOf( | |
75 ignoringImpCasts(stringLiteral(hasSize(0))), | |
76 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))), | |
77 ignoringImplicit(DefaultConstructor), | |
78 cxxConstructExpr( | |
79 hasDeclaration(cxxConstructorDecl(isCopyConstructor())), | |
80 has(expr(ignoringImpCasts(DefaultConstructor)))), | |
81 cxxConstructExpr( | |
82 hasDeclaration(cxxConstructorDecl(isMoveConstructor())), | |
83 has(expr(ignoringImpCasts(DefaultConstructor))))); | |
84 // Match the object being compared. | |
85 const auto STLArg = | |
86 anyOf(unaryOperator( | |
87 hasOperatorName("*"), | |
88 hasUnaryOperand( | |
89 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))), | |
90 expr(hasType(ValidContainer)).bind("STLObject")); | |
91 Finder->addMatcher( | |
92 cxxOperatorCallExpr( | |
93 anyOf(hasOverloadedOperatorName("=="), | |
94 hasOverloadedOperatorName("!=")), | |
95 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)), | |
96 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))), | |
97 unless(hasAncestor( | |
98 cxxMethodDecl(ofClass(equalsBoundNode("container")))))) | |
99 .bind("BinCmp"), | |
100 this); | |
101 } | |
102 | |
103 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { | |
104 const auto *MemberCall = | |
105 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr"); | |
106 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp"); | |
107 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp"); | |
108 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee"); | |
109 const auto *E = | |
110 MemberCall | |
111 ? MemberCall->getImplicitObjectArgument() | |
112 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject")); | |
113 FixItHint Hint; | |
114 std::string ReplacementText = std::string( | |
115 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), | |
116 *Result.SourceManager, getLangOpts())); | |
117 if (BinCmp && IsBinaryOrTernary(E)) { | |
118 // Not just a DeclRefExpr, so parenthesize to be on the safe side. | |
119 ReplacementText = "(" + ReplacementText + ")"; | |
120 } | |
121 if (E->getType()->isPointerType()) | |
122 ReplacementText += "->empty()"; | |
123 else | |
124 ReplacementText += ".empty()"; | |
125 | |
126 if (BinCmp) { | |
127 if (BinCmp->getOperator() == OO_ExclaimEqual) { | |
128 ReplacementText = "!" + ReplacementText; | |
129 } | |
130 Hint = | |
131 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText); | |
132 } else if (BinaryOp) { // Determine the correct transformation. | |
133 bool Negation = false; | |
134 const bool ContainerIsLHS = | |
135 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); | |
136 const auto OpCode = BinaryOp->getOpcode(); | |
137 uint64_t Value = 0; | |
138 if (ContainerIsLHS) { | |
139 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>( | |
140 BinaryOp->getRHS()->IgnoreImpCasts())) | |
141 Value = Literal->getValue().getLimitedValue(); | |
142 else | |
143 return; | |
144 } else { | |
145 Value = | |
146 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()) | |
147 ->getValue() | |
148 .getLimitedValue(); | |
149 } | |
150 | |
151 // Constant that is not handled. | |
152 if (Value > 1) | |
153 return; | |
154 | |
155 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || | |
156 OpCode == BinaryOperatorKind::BO_NE)) | |
157 return; | |
158 | |
159 // Always true, no warnings for that. | |
160 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || | |
161 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) | |
162 return; | |
163 | |
164 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. | |
165 if (Value == 1) { | |
166 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || | |
167 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) | |
168 return; | |
169 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || | |
170 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) | |
171 return; | |
172 } | |
173 | |
174 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) | |
175 Negation = true; | |
176 if ((OpCode == BinaryOperatorKind::BO_GT || | |
177 OpCode == BinaryOperatorKind::BO_GE) && | |
178 ContainerIsLHS) | |
179 Negation = true; | |
180 if ((OpCode == BinaryOperatorKind::BO_LT || | |
181 OpCode == BinaryOperatorKind::BO_LE) && | |
182 !ContainerIsLHS) | |
183 Negation = true; | |
184 | |
185 if (Negation) | |
186 ReplacementText = "!" + ReplacementText; | |
187 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), | |
188 ReplacementText); | |
189 | |
190 } else { | |
191 // If there is a conversion above the size call to bool, it is safe to just | |
192 // replace size with empty. | |
193 if (const auto *UnaryOp = | |
194 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize")) | |
195 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), | |
196 ReplacementText); | |
197 else | |
198 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), | |
199 "!" + ReplacementText); | |
200 } | |
201 | |
202 if (MemberCall) { | |
203 diag(MemberCall->getBeginLoc(), | |
204 "the 'empty' method should be used to check " | |
205 "for emptiness instead of 'size'") | |
206 << Hint; | |
207 } else { | |
208 diag(BinCmp->getBeginLoc(), | |
209 "the 'empty' method should be used to check " | |
210 "for emptiness instead of comparing to an empty object") | |
211 << Hint; | |
212 } | |
213 | |
214 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container"); | |
215 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) { | |
216 // The definition of the empty() method is the same for all implicit | |
217 // instantiations. In order to avoid duplicate or inconsistent warnings | |
218 // (depending on how deduplication is done), we use the same class name | |
219 // for all implicit instantiations of a template. | |
220 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation) | |
221 Container = CTS->getSpecializedTemplate(); | |
222 } | |
223 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); | |
224 | |
225 diag(Empty->getLocation(), "method %0::empty() defined here", | |
226 DiagnosticIDs::Note) | |
227 << Container; | |
228 } | |
229 | |
230 } // namespace readability | |
231 } // namespace tidy | |
232 } // namespace clang |