|  | //===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | #include "ContainerSizeEmptyCheck.h" | 
|  | #include "../utils/ASTUtils.h" | 
|  | #include "../utils/Matchers.h" | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/ASTMatchers/ASTMatchers.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  | #include "llvm/ADT/StringRef.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang { | 
|  | namespace tidy { | 
|  | namespace readability { | 
|  |  | 
|  | using utils::IsBinaryOrTernary; | 
|  |  | 
|  | ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context) {} | 
|  |  | 
|  | void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { | 
|  | // Only register the matchers for C++; the functionality currently does not | 
|  | // provide any benefit to other languages, despite being benign. | 
|  | if (!getLangOpts().CPlusPlus) | 
|  | return; | 
|  |  | 
|  | const auto ValidContainer = qualType(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom( | 
|  | namedDecl( | 
|  | has(cxxMethodDecl( | 
|  | isConst(), parameterCountIs(0), isPublic(), | 
|  | hasName("size"), | 
|  | returns(qualType(isInteger(), unless(booleanType())))) | 
|  | .bind("size")), | 
|  | has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), | 
|  | hasName("empty"), returns(booleanType())) | 
|  | .bind("empty"))) | 
|  | .bind("container"))))))); | 
|  |  | 
|  | const auto WrongUse = anyOf( | 
|  | hasParent(binaryOperator( | 
|  | matchers::isComparisonOperator(), | 
|  | hasEitherOperand(ignoringImpCasts(anyOf( | 
|  | integerLiteral(equals(1)), integerLiteral(equals(0)))))) | 
|  | .bind("SizeBinaryOp")), | 
|  | hasParent(implicitCastExpr( | 
|  | hasImplicitDestinationType(booleanType()), | 
|  | anyOf( | 
|  | hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")), | 
|  | anything()))), | 
|  | hasParent(explicitCastExpr(hasDestinationType(booleanType())))); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), | 
|  | hasType(pointsTo(ValidContainer)), | 
|  | hasType(references(ValidContainer))))), | 
|  | callee(cxxMethodDecl(hasName("size"))), WrongUse, | 
|  | unless(hasAncestor(cxxMethodDecl( | 
|  | ofClass(equalsBoundNode("container")))))) | 
|  | .bind("SizeCallExpr"), | 
|  | this); | 
|  |  | 
|  | // Empty constructor matcher. | 
|  | const auto DefaultConstructor = cxxConstructExpr( | 
|  | hasDeclaration(cxxConstructorDecl(isDefaultConstructor()))); | 
|  | // Comparison to empty string or empty constructor. | 
|  | const auto WrongComparend = anyOf( | 
|  | ignoringImpCasts(stringLiteral(hasSize(0))), | 
|  | ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))), | 
|  | ignoringImplicit(DefaultConstructor), | 
|  | cxxConstructExpr( | 
|  | hasDeclaration(cxxConstructorDecl(isCopyConstructor())), | 
|  | has(expr(ignoringImpCasts(DefaultConstructor)))), | 
|  | cxxConstructExpr( | 
|  | hasDeclaration(cxxConstructorDecl(isMoveConstructor())), | 
|  | has(expr(ignoringImpCasts(DefaultConstructor))))); | 
|  | // Match the object being compared. | 
|  | const auto STLArg = | 
|  | anyOf(unaryOperator( | 
|  | hasOperatorName("*"), | 
|  | hasUnaryOperand( | 
|  | expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))), | 
|  | expr(hasType(ValidContainer)).bind("STLObject")); | 
|  | Finder->addMatcher( | 
|  | cxxOperatorCallExpr( | 
|  | anyOf(hasOverloadedOperatorName("=="), | 
|  | hasOverloadedOperatorName("!=")), | 
|  | anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)), | 
|  | allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))), | 
|  | unless(hasAncestor( | 
|  | cxxMethodDecl(ofClass(equalsBoundNode("container")))))) | 
|  | .bind("BinCmp"), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | const auto *MemberCall = | 
|  | Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr"); | 
|  | const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp"); | 
|  | const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp"); | 
|  | const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee"); | 
|  | const auto *E = | 
|  | MemberCall | 
|  | ? MemberCall->getImplicitObjectArgument() | 
|  | : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject")); | 
|  | FixItHint Hint; | 
|  | std::string ReplacementText = | 
|  | Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), | 
|  | *Result.SourceManager, getLangOpts()); | 
|  | if (BinCmp && IsBinaryOrTernary(E)) { | 
|  | // Not just a DeclRefExpr, so parenthesize to be on the safe side. | 
|  | ReplacementText = "(" + ReplacementText + ")"; | 
|  | } | 
|  | if (E->getType()->isPointerType()) | 
|  | ReplacementText += "->empty()"; | 
|  | else | 
|  | ReplacementText += ".empty()"; | 
|  |  | 
|  | if (BinCmp) { | 
|  | if (BinCmp->getOperator() == OO_ExclaimEqual) { | 
|  | ReplacementText = "!" + ReplacementText; | 
|  | } | 
|  | Hint = | 
|  | FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText); | 
|  | } else if (BinaryOp) {  // Determine the correct transformation. | 
|  | bool Negation = false; | 
|  | const bool ContainerIsLHS = | 
|  | !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); | 
|  | const auto OpCode = BinaryOp->getOpcode(); | 
|  | uint64_t Value = 0; | 
|  | if (ContainerIsLHS) { | 
|  | if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>( | 
|  | BinaryOp->getRHS()->IgnoreImpCasts())) | 
|  | Value = Literal->getValue().getLimitedValue(); | 
|  | else | 
|  | return; | 
|  | } else { | 
|  | Value = | 
|  | llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()) | 
|  | ->getValue() | 
|  | .getLimitedValue(); | 
|  | } | 
|  |  | 
|  | // Constant that is not handled. | 
|  | if (Value > 1) | 
|  | return; | 
|  |  | 
|  | if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || | 
|  | OpCode == BinaryOperatorKind::BO_NE)) | 
|  | return; | 
|  |  | 
|  | // Always true, no warnings for that. | 
|  | if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || | 
|  | (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) | 
|  | return; | 
|  |  | 
|  | // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. | 
|  | if (Value == 1) { | 
|  | if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || | 
|  | (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) | 
|  | return; | 
|  | if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || | 
|  | (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) | 
|  | Negation = true; | 
|  | if ((OpCode == BinaryOperatorKind::BO_GT || | 
|  | OpCode == BinaryOperatorKind::BO_GE) && | 
|  | ContainerIsLHS) | 
|  | Negation = true; | 
|  | if ((OpCode == BinaryOperatorKind::BO_LT || | 
|  | OpCode == BinaryOperatorKind::BO_LE) && | 
|  | !ContainerIsLHS) | 
|  | Negation = true; | 
|  |  | 
|  | if (Negation) | 
|  | ReplacementText = "!" + ReplacementText; | 
|  | Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), | 
|  | ReplacementText); | 
|  |  | 
|  | } else { | 
|  | // If there is a conversion above the size call to bool, it is safe to just | 
|  | // replace size with empty. | 
|  | if (const auto *UnaryOp = | 
|  | Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize")) | 
|  | Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), | 
|  | ReplacementText); | 
|  | else | 
|  | Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), | 
|  | "!" + ReplacementText); | 
|  | } | 
|  |  | 
|  | if (MemberCall) { | 
|  | diag(MemberCall->getLocStart(), | 
|  | "the 'empty' method should be used to check " | 
|  | "for emptiness instead of 'size'") | 
|  | << Hint; | 
|  | } else { | 
|  | diag(BinCmp->getLocStart(), | 
|  | "the 'empty' method should be used to check " | 
|  | "for emptiness instead of comparing to an empty object") | 
|  | << Hint; | 
|  | } | 
|  |  | 
|  | const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container"); | 
|  | const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); | 
|  |  | 
|  | diag(Empty->getLocation(), "method %0::empty() defined here", | 
|  | DiagnosticIDs::Note) | 
|  | << Container; | 
|  | } | 
|  |  | 
|  | } // namespace readability | 
|  | } // namespace tidy | 
|  | } // namespace clang |