RedundantStringInitCheck.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. //===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===//
  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-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "RedundantStringInitCheck.h"
  9. #include "../utils/Matchers.h"
  10. #include "../utils/OptionsUtils.h"
  11. #include "clang/ASTMatchers/ASTMatchers.h"
  12. #include <optional>
  13. using namespace clang::ast_matchers;
  14. using namespace clang::tidy::matchers;
  15. namespace clang::tidy::readability {
  16. const char DefaultStringNames[] =
  17. "::std::basic_string_view;::std::basic_string";
  18. static std::vector<StringRef> removeNamespaces(ArrayRef<StringRef> Names) {
  19. std::vector<StringRef> Result;
  20. Result.reserve(Names.size());
  21. for (StringRef Name : Names) {
  22. StringRef::size_type ColonPos = Name.rfind(':');
  23. Result.push_back(
  24. Name.drop_front(ColonPos == StringRef::npos ? 0 : ColonPos + 1));
  25. }
  26. return Result;
  27. }
  28. static const CXXConstructExpr *
  29. getConstructExpr(const CXXCtorInitializer &CtorInit) {
  30. const Expr *InitExpr = CtorInit.getInit();
  31. if (const auto *CleanUpExpr = dyn_cast<ExprWithCleanups>(InitExpr))
  32. InitExpr = CleanUpExpr->getSubExpr();
  33. return dyn_cast<CXXConstructExpr>(InitExpr);
  34. }
  35. static std::optional<SourceRange>
  36. getConstructExprArgRange(const CXXConstructExpr &Construct) {
  37. SourceLocation B, E;
  38. for (const Expr *Arg : Construct.arguments()) {
  39. if (B.isInvalid())
  40. B = Arg->getBeginLoc();
  41. if (Arg->getEndLoc().isValid())
  42. E = Arg->getEndLoc();
  43. }
  44. if (B.isInvalid() || E.isInvalid())
  45. return std::nullopt;
  46. return SourceRange(B, E);
  47. }
  48. RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name,
  49. ClangTidyContext *Context)
  50. : ClangTidyCheck(Name, Context),
  51. StringNames(utils::options::parseStringList(
  52. Options.get("StringNames", DefaultStringNames))) {}
  53. void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  54. Options.store(Opts, "StringNames", DefaultStringNames);
  55. }
  56. void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) {
  57. const auto HasStringTypeName = hasAnyName(StringNames);
  58. const auto HasStringCtorName = hasAnyName(removeNamespaces(StringNames));
  59. // Match string constructor.
  60. const auto StringConstructorExpr = expr(
  61. anyOf(cxxConstructExpr(argumentCountIs(1),
  62. hasDeclaration(cxxMethodDecl(HasStringCtorName))),
  63. // If present, the second argument is the alloc object which must
  64. // not be present explicitly.
  65. cxxConstructExpr(argumentCountIs(2),
  66. hasDeclaration(cxxMethodDecl(HasStringCtorName)),
  67. hasArgument(1, cxxDefaultArgExpr()))));
  68. // Match a string constructor expression with an empty string literal.
  69. const auto EmptyStringCtorExpr = cxxConstructExpr(
  70. StringConstructorExpr,
  71. hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0)))));
  72. const auto EmptyStringCtorExprWithTemporaries =
  73. cxxConstructExpr(StringConstructorExpr,
  74. hasArgument(0, ignoringImplicit(EmptyStringCtorExpr)));
  75. const auto StringType = hasType(hasUnqualifiedDesugaredType(
  76. recordType(hasDeclaration(cxxRecordDecl(HasStringTypeName)))));
  77. const auto EmptyStringInit = traverse(
  78. TK_AsIs, expr(ignoringImplicit(anyOf(
  79. EmptyStringCtorExpr, EmptyStringCtorExprWithTemporaries))));
  80. // Match a variable declaration with an empty string literal as initializer.
  81. // Examples:
  82. // string foo = "";
  83. // string bar("");
  84. Finder->addMatcher(
  85. traverse(TK_AsIs,
  86. namedDecl(varDecl(StringType, hasInitializer(EmptyStringInit))
  87. .bind("vardecl"),
  88. unless(parmVarDecl()))),
  89. this);
  90. // Match a field declaration with an empty string literal as initializer.
  91. Finder->addMatcher(
  92. namedDecl(fieldDecl(StringType, hasInClassInitializer(EmptyStringInit))
  93. .bind("fieldDecl")),
  94. this);
  95. // Matches Constructor Initializers with an empty string literal as
  96. // initializer.
  97. // Examples:
  98. // Foo() : SomeString("") {}
  99. Finder->addMatcher(
  100. cxxCtorInitializer(
  101. isWritten(),
  102. forField(allOf(StringType, optionally(hasInClassInitializer(
  103. EmptyStringInit.bind("empty_init"))))),
  104. withInitializer(EmptyStringInit))
  105. .bind("ctorInit"),
  106. this);
  107. }
  108. void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) {
  109. if (const auto *VDecl = Result.Nodes.getNodeAs<VarDecl>("vardecl")) {
  110. // VarDecl's getSourceRange() spans 'string foo = ""' or 'string bar("")'.
  111. // So start at getLocation() to span just 'foo = ""' or 'bar("")'.
  112. SourceRange ReplaceRange(VDecl->getLocation(), VDecl->getEndLoc());
  113. diag(VDecl->getLocation(), "redundant string initialization")
  114. << FixItHint::CreateReplacement(ReplaceRange, VDecl->getName());
  115. }
  116. if (const auto *FDecl = Result.Nodes.getNodeAs<FieldDecl>("fieldDecl")) {
  117. // FieldDecl's getSourceRange() spans 'string foo = ""'.
  118. // So start at getLocation() to span just 'foo = ""'.
  119. SourceRange ReplaceRange(FDecl->getLocation(), FDecl->getEndLoc());
  120. diag(FDecl->getLocation(), "redundant string initialization")
  121. << FixItHint::CreateReplacement(ReplaceRange, FDecl->getName());
  122. }
  123. if (const auto *CtorInit =
  124. Result.Nodes.getNodeAs<CXXCtorInitializer>("ctorInit")) {
  125. if (const FieldDecl *Member = CtorInit->getMember()) {
  126. if (!Member->hasInClassInitializer() ||
  127. Result.Nodes.getNodeAs<Expr>("empty_init")) {
  128. // The String isn't declared in the class with an initializer or its
  129. // declared with a redundant initializer, which will be removed. Either
  130. // way the string will be default initialized, therefore we can remove
  131. // the constructor initializer entirely.
  132. diag(CtorInit->getMemberLocation(), "redundant string initialization")
  133. << FixItHint::CreateRemoval(CtorInit->getSourceRange());
  134. return;
  135. }
  136. }
  137. const CXXConstructExpr *Construct = getConstructExpr(*CtorInit);
  138. if (!Construct)
  139. return;
  140. if (std::optional<SourceRange> RemovalRange =
  141. getConstructExprArgRange(*Construct))
  142. diag(CtorInit->getMemberLocation(), "redundant string initialization")
  143. << FixItHint::CreateRemoval(*RemovalRange);
  144. }
  145. }
  146. } // namespace clang::tidy::readability