diff options
author | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 13:58:24 +0300 |
---|---|---|
committer | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 14:11:53 +0300 |
commit | 11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch) | |
tree | fabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp | |
parent | 9685917341315774aad5733b1793b1e533a88bbb (diff) | |
download | ydb-11a895b7e15d1c5a1f52706396b82e3f9db953cb.tar.gz |
Export clang-format16 via ydblib project
6e6be3a95868fde888d801b7590af4044049563f
Diffstat (limited to 'contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp')
-rw-r--r-- | contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp new file mode 100644 index 0000000000..a39b72e39c --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,331 @@ +//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#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 ast_matchers { +AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, + AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, + CXXConstructExpr), + internal::Matcher<Expr>, ArgMatcher, + internal::Matcher<ParmVarDecl>, ParamMatcher) { + BoundNodesTreeBuilder Result; + // The first argument of an overloaded member operator is the implicit object + // argument of the method which should not be matched against a parameter, so + // we skip over it here. + BoundNodesTreeBuilder Matches; + unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl())) + .matches(Node, Finder, &Matches) + ? 1 + : 0; + int ParamIndex = 0; + for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) { + BoundNodesTreeBuilder ArgMatches(*Builder); + if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder, + &ArgMatches)) { + BoundNodesTreeBuilder ParamMatches(ArgMatches); + if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + hasParameter(ParamIndex, ParamMatcher)))), + callExpr(callee(functionDecl( + hasParameter(ParamIndex, ParamMatcher)))))) + .matches(Node, Finder, &ParamMatches)) { + Result.addMatch(ParamMatches); + *Builder = std::move(Result); + return true; + } + } + ++ParamIndex; + } + return false; +} + +AST_MATCHER(Expr, usedInBooleanContext) { + const char *ExprName = "__booleanContextExpr"; + auto Result = + expr(expr().bind(ExprName), + anyOf(hasParent( + mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))), + hasParent(cxxConstructorDecl( + hasAnyConstructorInitializer(cxxCtorInitializer( + withInitializer(expr(equalsBoundNode(ExprName))), + forField(hasType(booleanType())))))), + hasParent(stmt(anyOf( + explicitCastExpr(hasDestinationType(booleanType())), + mapAnyOf(ifStmt, doStmt, whileStmt, forStmt, + conditionalOperator) + .with(hasCondition(expr(equalsBoundNode(ExprName)))), + parenListExpr(hasParent(varDecl(hasType(booleanType())))), + parenExpr(hasParent( + explicitCastExpr(hasDestinationType(booleanType())))), + returnStmt(forFunction(returns(booleanType()))), + cxxUnresolvedConstructExpr(hasType(booleanType())), + invocation(hasAnyArgumentWithParam( + expr(equalsBoundNode(ExprName)), + parmVarDecl(hasType(booleanType())))), + binaryOperator(hasAnyOperatorName("&&", "||")), + unaryOperator(hasOperatorName("!")).bind("NegOnSize")))))) + .matches(Node, Finder, Builder); + Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) { + return Nodes.getNode(ExprName).getNodeKind().isNone(); + }); + return Result; +} +AST_MATCHER(CXXConstructExpr, isDefaultConstruction) { + return Node.getConstructor()->isDefaultConstructor(); +} +AST_MATCHER(QualType, isIntegralType) { + return Node->isIntegralType(Finder->getASTContext()); +} +} // namespace ast_matchers +namespace tidy::readability { + +using utils::isBinaryOrTernary; + +ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { + const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom( + namedDecl( + has(cxxMethodDecl( + isConst(), parameterCountIs(0), isPublic(), hasName("size"), + returns(qualType(isIntegralType(), unless(booleanType())))) + .bind("size")), + has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), + hasName("empty"), returns(booleanType())) + .bind("empty"))) + .bind("container"))); + + const auto ValidContainerNonTemplateType = + qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(ValidContainerRecord)))); + const auto ValidContainerTemplateType = + qualType(hasUnqualifiedDesugaredType(templateSpecializationType( + hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))))); + + const auto ValidContainer = qualType( + anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType)); + + const auto WrongUse = + anyOf(hasParent(binaryOperator( + isComparisonOperator(), + hasEitherOperand(anyOf(integerLiteral(equals(1)), + integerLiteral(equals(0))))) + .bind("SizeBinaryOp")), + usedInBooleanContext()); + + Finder->addMatcher( + cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer)))) + .bind("MemberCallObject")), + callee(cxxMethodDecl(hasName("size"))), WrongUse, + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); + + Finder->addMatcher( + callExpr(has(cxxDependentScopeMemberExpr( + hasObjectExpression( + expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer)))) + .bind("MemberCallObject")), + hasMemberName("size"))), + WrongUse, + unless(hasAncestor( + cxxMethodDecl(ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); + + // Comparison to empty string or empty constructor. + const auto WrongComparend = anyOf( + stringLiteral(hasSize(0)), cxxConstructExpr(isDefaultConstruction()), + cxxUnresolvedConstructExpr(argumentCountIs(0))); + // 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( + binaryOperation(hasAnyOperatorName("==", "!="), + hasOperands(WrongComparend, + STLArg), + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("BinCmp"), + this); +} + +void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr"); + const auto *MemberCallObject = + Result.Nodes.getNodeAs<Expr>("MemberCallObject"); + const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp"); + const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp"); + const auto *BinCmpRewritten = + Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp"); + const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp"); + const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee"); + const auto *E = + MemberCallObject + ? MemberCallObject + : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject")); + FixItHint Hint; + std::string ReplacementText = std::string( + Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, getLangOpts())); + const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E); + if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) || + (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) { + ReplacementText = "(" + ReplacementText + ")"; + } + if (OpCallExpr && + OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) { + // This can happen if the object is a smart pointer. Don't add anything + // because a '->' is already there (PR#51776), just call the method. + ReplacementText += "empty()"; + } else 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 (BinCmpTempl) { + if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) { + ReplacementText = "!" + ReplacementText; + } + Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(), + ReplacementText); + } else if (BinCmpRewritten) { + if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) { + ReplacementText = "!" + ReplacementText; + } + Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(), + ReplacementText); + } else if (BinaryOp) { // Determine the correct transformation. + const auto *LiteralLHS = + llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); + const auto *LiteralRHS = + llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts()); + const bool ContainerIsLHS = !LiteralLHS; + + uint64_t Value = 0; + if (LiteralLHS) + Value = LiteralLHS->getValue().getLimitedValue(); + else if (LiteralRHS) + Value = LiteralRHS->getValue().getLimitedValue(); + else + return; + + bool Negation = false; + const auto OpCode = BinaryOp->getOpcode(); + + // 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); + } + + auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{}; + + if (WarnLoc.isValid()) { + diag(WarnLoc, "the 'empty' method should be used to check " + "for emptiness instead of 'size'") + << Hint; + } else { + WarnLoc = BinCmpTempl + ? BinCmpTempl->getBeginLoc() + : (BinCmp ? BinCmp->getBeginLoc() + : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc() + : SourceLocation{})); + diag(WarnLoc, "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"); + if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) { + // The definition of the empty() method is the same for all implicit + // instantiations. In order to avoid duplicate or inconsistent warnings + // (depending on how deduplication is done), we use the same class name + // for all implicit instantiations of a template. + if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation) + Container = CTS->getSpecializedTemplate(); + } + const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); + + diag(Empty->getLocation(), "method %0::empty() defined here", + DiagnosticIDs::Note) + << Container; +} + +} // namespace tidy::readability +} // namespace clang |