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