aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
diff options
context:
space:
mode:
authorthegeorg <thegeorg@yandex-team.com>2024-03-13 13:58:24 +0300
committerthegeorg <thegeorg@yandex-team.com>2024-03-13 14:11:53 +0300
commit11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch)
treefabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
parent9685917341315774aad5733b1793b1e533a88bbb (diff)
downloadydb-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.cpp331
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