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 { 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 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 } 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 95 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, 96 ClangTidyContext *Context) 97 : ClangTidyCheck(Name, Context) {} 98 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 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 if (IsBinaryOrTernary(E) || isa<UnaryOperator>(E)) { 195 ReplacementText = "(" + ReplacementText + ")"; 196 } 197 if (E->getType()->isPointerType()) 198 ReplacementText += "->empty()"; 199 else 200 ReplacementText += ".empty()"; 201 202 if (BinCmp) { 203 if (BinCmp->getOperator() == OO_ExclaimEqual) { 204 ReplacementText = "!" + ReplacementText; 205 } 206 Hint = 207 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText); 208 } else if (BinCmpTempl) { 209 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) { 210 ReplacementText = "!" + ReplacementText; 211 } 212 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(), 213 ReplacementText); 214 } else if (BinCmpRewritten) { 215 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) { 216 ReplacementText = "!" + ReplacementText; 217 } 218 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(), 219 ReplacementText); 220 } else if (BinaryOp) { // Determine the correct transformation. 221 bool Negation = false; 222 const bool ContainerIsLHS = 223 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); 224 const auto OpCode = BinaryOp->getOpcode(); 225 uint64_t Value = 0; 226 if (ContainerIsLHS) { 227 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>( 228 BinaryOp->getRHS()->IgnoreImpCasts())) 229 Value = Literal->getValue().getLimitedValue(); 230 else 231 return; 232 } else { 233 Value = 234 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()) 235 ->getValue() 236 .getLimitedValue(); 237 } 238 239 // Constant that is not handled. 240 if (Value > 1) 241 return; 242 243 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || 244 OpCode == BinaryOperatorKind::BO_NE)) 245 return; 246 247 // Always true, no warnings for that. 248 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || 249 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) 250 return; 251 252 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. 253 if (Value == 1) { 254 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || 255 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) 256 return; 257 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || 258 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) 259 return; 260 } 261 262 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) 263 Negation = true; 264 if ((OpCode == BinaryOperatorKind::BO_GT || 265 OpCode == BinaryOperatorKind::BO_GE) && 266 ContainerIsLHS) 267 Negation = true; 268 if ((OpCode == BinaryOperatorKind::BO_LT || 269 OpCode == BinaryOperatorKind::BO_LE) && 270 !ContainerIsLHS) 271 Negation = true; 272 273 if (Negation) 274 ReplacementText = "!" + ReplacementText; 275 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), 276 ReplacementText); 277 278 } else { 279 // If there is a conversion above the size call to bool, it is safe to just 280 // replace size with empty. 281 if (const auto *UnaryOp = 282 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize")) 283 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), 284 ReplacementText); 285 else 286 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), 287 "!" + ReplacementText); 288 } 289 290 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{}; 291 292 if (WarnLoc.isValid()) { 293 diag(WarnLoc, "the 'empty' method should be used to check " 294 "for emptiness instead of 'size'") 295 << Hint; 296 } else { 297 WarnLoc = BinCmpTempl 298 ? BinCmpTempl->getBeginLoc() 299 : (BinCmp ? BinCmp->getBeginLoc() 300 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc() 301 : SourceLocation{})); 302 diag(WarnLoc, "the 'empty' method should be used to check " 303 "for emptiness instead of comparing to an empty object") 304 << Hint; 305 } 306 307 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container"); 308 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) { 309 // The definition of the empty() method is the same for all implicit 310 // instantiations. In order to avoid duplicate or inconsistent warnings 311 // (depending on how deduplication is done), we use the same class name 312 // for all implicit instantiations of a template. 313 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation) 314 Container = CTS->getSpecializedTemplate(); 315 } 316 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); 317 318 diag(Empty->getLocation(), "method %0::empty() defined here", 319 DiagnosticIDs::Note) 320 << Container; 321 } 322 323 } // namespace readability 324 } // namespace tidy 325 } // namespace clang 326