437 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8#include "ContainerSizeEmptyCheck.h"9#include "../utils/ASTUtils.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.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 17using namespace clang::ast_matchers;18 19namespace clang::tidy::readability {20 21namespace {22 23AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,24 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,25 CXXConstructExpr),26 ast_matchers::internal::Matcher<Expr>, ArgMatcher,27 ast_matchers::internal::Matcher<ParmVarDecl>,28 ParamMatcher) {29 ast_matchers::internal::BoundNodesTreeBuilder Result;30 // The first argument of an overloaded member operator is the implicit object31 // argument of the method which should not be matched against a parameter, so32 // we skip over it here.33 ast_matchers::internal::BoundNodesTreeBuilder Matches;34 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))35 .matches(Node, Finder, &Matches)36 ? 137 : 0;38 int ParamIndex = 0;39 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {40 ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder);41 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,42 &ArgMatches)) {43 ast_matchers::internal::BoundNodesTreeBuilder ParamMatches(ArgMatches);44 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(45 hasParameter(ParamIndex, ParamMatcher)))),46 callExpr(callee(functionDecl(47 hasParameter(ParamIndex, ParamMatcher))))))48 .matches(Node, Finder, &ParamMatches)) {49 Result.addMatch(ParamMatches);50 *Builder = std::move(Result);51 return true;52 }53 }54 ++ParamIndex;55 }56 return false;57}58 59AST_MATCHER(Expr, usedInBooleanContext) {60 const char *ExprName = "__booleanContextExpr";61 auto Result =62 expr(expr().bind(ExprName),63 anyOf(hasParent(64 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),65 hasParent(cxxConstructorDecl(66 hasAnyConstructorInitializer(cxxCtorInitializer(67 withInitializer(expr(equalsBoundNode(ExprName))),68 forField(hasType(booleanType())))))),69 hasParent(stmt(anyOf(70 explicitCastExpr(hasDestinationType(booleanType())),71 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,72 conditionalOperator)73 .with(hasCondition(expr(equalsBoundNode(ExprName)))),74 parenListExpr(hasParent(varDecl(hasType(booleanType())))),75 parenExpr(hasParent(76 explicitCastExpr(hasDestinationType(booleanType())))),77 returnStmt(forFunction(returns(booleanType()))),78 cxxUnresolvedConstructExpr(hasType(booleanType())),79 invocation(hasAnyArgumentWithParam(80 expr(equalsBoundNode(ExprName)),81 parmVarDecl(hasType(booleanType())))),82 binaryOperator(hasAnyOperatorName("&&", "||")),83 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))84 .matches(Node, Finder, Builder);85 Builder->removeBindings(86 [ExprName](const ast_matchers::internal::BoundNodesMap &Nodes) {87 return Nodes.getNode(ExprName).getNodeKind().isNone();88 });89 return Result;90}91 92AST_MATCHER(QualType, isIntegralType) {93 return Node->isIntegralType(Finder->getASTContext());94}95 96AST_MATCHER_P(UserDefinedLiteral, hasLiteral,97 clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {98 const UserDefinedLiteral::LiteralOperatorKind LOK =99 Node.getLiteralOperatorKind();100 if (LOK == UserDefinedLiteral::LOK_Template ||101 LOK == UserDefinedLiteral::LOK_Raw)102 return false;103 104 if (const Expr *CookedLiteral = Node.getCookedLiteral())105 return InnerMatcher.matches(*CookedLiteral, Finder, Builder);106 return false;107}108 109AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,110 clang::ast_matchers::internal::Matcher<CXXMethodDecl>,111 InnerMatcher) {112 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);113}114 115AST_POLYMORPHIC_MATCHER_P(116 matchMemberName,117 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),118 std::string, MemberName) {119 if (const auto *E = dyn_cast<MemberExpr>(&Node))120 return E->getMemberDecl()->getName() == MemberName;121 122 if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node))123 return E->getMember().getAsString() == MemberName;124 125 return false;126}127 128} // namespace129 130using utils::isBinaryOrTernary;131 132ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,133 ClangTidyContext *Context)134 : ClangTidyCheck(Name, Context),135 ExcludedComparisonTypes(utils::options::parseStringList(136 Options.get("ExcludedComparisonTypes", "::std::array"))) {}137 138void ContainerSizeEmptyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {139 Options.store(Opts, "ExcludedComparisonTypes",140 utils::options::serializeStringList(ExcludedComparisonTypes));141}142 143void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {144 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(145 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),146 hasAnyName("size", "length"),147 returns(qualType(isIntegralType(),148 unless(booleanType()))))149 .bind("size")),150 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),151 hasName("empty"), returns(booleanType()))152 .bind("empty")))153 .bind("container")));154 155 const auto ValidContainerNonTemplateType =156 qualType(hasUnqualifiedDesugaredType(157 recordType(hasDeclaration(ValidContainerRecord))));158 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(159 anyOf(templateSpecializationType(160 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),161 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));162 163 const auto ValidContainer = qualType(164 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));165 166 const auto WrongUse =167 anyOf(hasParent(binaryOperator(168 isComparisonOperator(),169 hasEitherOperand(anyOf(integerLiteral(equals(1)),170 integerLiteral(equals(0)))))171 .bind("SizeBinaryOp")),172 usedInBooleanContext());173 174 const auto NotInEmptyMethodOfContainer = unless(175 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));176 177 Finder->addMatcher(178 cxxMemberCallExpr(179 argumentCountIs(0),180 on(expr(anyOf(hasType(ValidContainer),181 hasType(pointsTo(ValidContainer)),182 hasType(references(ValidContainer))))183 .bind("MemberCallObject")),184 callee(185 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),186 WrongUse, NotInEmptyMethodOfContainer)187 .bind("SizeCallExpr"),188 this);189 190 Finder->addMatcher(191 callExpr(192 argumentCountIs(0),193 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)194 .with(195 hasObjectExpression(196 expr(anyOf(hasType(ValidContainer),197 hasType(pointsTo(ValidContainer)),198 hasType(references(ValidContainer))))199 .bind("MemberCallObject")),200 anyOf(matchMemberName("size"), matchMemberName("length")))201 .bind("MemberExpr")),202 WrongUse, NotInEmptyMethodOfContainer)203 .bind("SizeCallExpr"),204 this);205 206 // Comparison to empty string or empty constructor.207 const auto WrongComparend =208 anyOf(stringLiteral(hasSize(0)),209 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),210 cxxConstructExpr(argumentCountIs(0)),211 cxxUnresolvedConstructExpr(argumentCountIs(0)));212 // Match the object being compared.213 const auto STLArg =214 anyOf(unaryOperator(215 hasOperatorName("*"),216 hasUnaryOperand(217 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),218 expr(hasType(ValidContainer)).bind("STLObject"));219 220 const auto ExcludedComparisonTypesMatcher = qualType(anyOf(221 hasDeclaration(222 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))223 .bind("excluded")),224 hasCanonicalType(hasDeclaration(225 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))226 .bind("excluded")))));227 const auto SameExcludedComparisonTypesMatcher =228 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),229 hasCanonicalType(hasDeclaration(230 cxxRecordDecl(equalsBoundNode("excluded"))))));231 232 Finder->addMatcher(233 binaryOperation(234 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),235 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),236 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),237 NotInEmptyMethodOfContainer)238 .bind("BinCmp"),239 this);240}241 242void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {243 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");244 const auto *MemberCallObject =245 Result.Nodes.getNodeAs<Expr>("MemberCallObject");246 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");247 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");248 const auto *BinCmpRewritten =249 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");250 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");251 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");252 const auto *E =253 MemberCallObject254 ? MemberCallObject255 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));256 FixItHint Hint;257 std::string ReplacementText =258 E->isImplicitCXXThis()259 ? ""260 : std::string(Lexer::getSourceText(261 CharSourceRange::getTokenRange(E->getSourceRange()),262 *Result.SourceManager, getLangOpts()));263 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);264 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||265 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {266 ReplacementText = "(" + ReplacementText + ")";267 }268 if (OpCallExpr &&269 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {270 // This can happen if the object is a smart pointer. Don't add anything271 // because a '->' is already there (PR#51776), just call the method.272 ReplacementText += "empty()";273 } else if (E->isImplicitCXXThis()) {274 ReplacementText += "empty()";275 } else if (E->getType()->isPointerType())276 ReplacementText += "->empty()";277 else278 ReplacementText += ".empty()";279 280 if (BinCmp) {281 if (BinCmp->getOperator() == OO_ExclaimEqual) {282 ReplacementText = "!" + ReplacementText;283 }284 Hint =285 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);286 } else if (BinCmpTempl) {287 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {288 ReplacementText = "!" + ReplacementText;289 }290 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),291 ReplacementText);292 } else if (BinCmpRewritten) {293 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {294 ReplacementText = "!" + ReplacementText;295 }296 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),297 ReplacementText);298 } else if (BinaryOp) { // Determine the correct transformation.299 const auto *LiteralLHS =300 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());301 const auto *LiteralRHS =302 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());303 const bool ContainerIsLHS = !LiteralLHS;304 305 uint64_t Value = 0;306 if (LiteralLHS)307 Value = LiteralLHS->getValue().getLimitedValue();308 else if (LiteralRHS)309 Value = LiteralRHS->getValue().getLimitedValue();310 else311 return;312 313 bool Negation = false;314 const auto OpCode = BinaryOp->getOpcode();315 316 // Constant that is not handled.317 if (Value > 1)318 return;319 320 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||321 OpCode == BinaryOperatorKind::BO_NE))322 return;323 324 // Always true/false, no warnings for that.325 if (Value == 0) {326 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||327 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||328 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||329 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))330 return;331 }332 333 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.334 if (Value == 1) {335 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||336 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))337 return;338 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||339 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))340 return;341 }342 343 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed344 // types345 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&346 !ContainerIsLHS) ||347 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||348 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&349 !ContainerIsLHS) ||350 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {351 const Expr *Container = ContainerIsLHS352 ? BinaryOp->getLHS()->IgnoreImpCasts()353 : BinaryOp->getRHS()->IgnoreImpCasts();354 if (Container->getType()355 .getCanonicalType()356 .getNonReferenceType()357 ->isSignedIntegerType())358 return;359 }360 361 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)362 Negation = true;363 364 if ((OpCode == BinaryOperatorKind::BO_GT ||365 OpCode == BinaryOperatorKind::BO_GE) &&366 ContainerIsLHS)367 Negation = true;368 369 if ((OpCode == BinaryOperatorKind::BO_LT ||370 OpCode == BinaryOperatorKind::BO_LE) &&371 !ContainerIsLHS)372 Negation = true;373 374 if (Negation)375 ReplacementText = "!" + ReplacementText;376 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),377 ReplacementText);378 379 } else {380 // If there is a conversion above the size call to bool, it is safe to just381 // replace size with empty.382 if (const auto *UnaryOp =383 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))384 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),385 ReplacementText);386 else387 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),388 "!" + ReplacementText);389 }390 391 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};392 393 if (WarnLoc.isValid()) {394 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "395 "for emptiness instead of %0");396 if (const auto *SizeMethod =397 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))398 Diag << SizeMethod;399 else if (const auto *DependentExpr =400 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(401 "MemberExpr"))402 Diag << DependentExpr->getMember();403 else if (const auto *ME =404 Result.Nodes.getNodeAs<MemberExpr>("MemberExpr")) {405 Diag << ME->getMemberNameInfo().getName();406 } else407 Diag << "unknown method";408 Diag << Hint;409 } else {410 WarnLoc = BinCmpTempl411 ? BinCmpTempl->getBeginLoc()412 : (BinCmp ? BinCmp->getBeginLoc()413 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()414 : SourceLocation{}));415 diag(WarnLoc, "the 'empty' method should be used to check "416 "for emptiness instead of comparing to an empty object")417 << Hint;418 }419 420 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");421 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {422 // The definition of the empty() method is the same for all implicit423 // instantiations. In order to avoid duplicate or inconsistent warnings424 // (depending on how deduplication is done), we use the same class name425 // for all implicit instantiations of a template.426 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)427 Container = CTS->getSpecializedTemplate();428 }429 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");430 431 diag(Empty->getLocation(), "method %0::empty() defined here",432 DiagnosticIDs::Note)433 << Container;434}435 436} // namespace clang::tidy::readability437