RedundantStrcatCallsCheck.cpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //===--- RedundantStrcatCallsCheck.cpp - clang-tidy------------------------===//
  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 "RedundantStrcatCallsCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. using namespace clang::ast_matchers;
  12. namespace clang::tidy::abseil {
  13. // TODO: Features to add to the check:
  14. // - Make it work if num_args > 26.
  15. // - Remove empty literal string arguments.
  16. // - Collapse consecutive literal string arguments into one (remove the ,).
  17. // - Replace StrCat(a + b) -> StrCat(a, b) if a or b are strings.
  18. // - Make it work in macros if the outer and inner StrCats are both in the
  19. // argument.
  20. void RedundantStrcatCallsCheck::registerMatchers(MatchFinder* Finder) {
  21. const auto CallToStrcat =
  22. callExpr(callee(functionDecl(hasName("::absl::StrCat"))));
  23. const auto CallToStrappend =
  24. callExpr(callee(functionDecl(hasName("::absl::StrAppend"))));
  25. // Do not match StrCat() calls that are descendants of other StrCat calls.
  26. // Those are handled on the ancestor call.
  27. const auto CallToEither = callExpr(
  28. callee(functionDecl(hasAnyName("::absl::StrCat", "::absl::StrAppend"))));
  29. Finder->addMatcher(
  30. callExpr(CallToStrcat, unless(hasAncestor(CallToEither))).bind("StrCat"),
  31. this);
  32. Finder->addMatcher(CallToStrappend.bind("StrAppend"), this);
  33. }
  34. namespace {
  35. struct StrCatCheckResult {
  36. int NumCalls = 0;
  37. std::vector<FixItHint> Hints;
  38. };
  39. void removeCallLeaveArgs(const CallExpr *Call, StrCatCheckResult *CheckResult) {
  40. if (Call->getNumArgs() == 0)
  41. return;
  42. // Remove 'Foo('
  43. CheckResult->Hints.push_back(
  44. FixItHint::CreateRemoval(CharSourceRange::getCharRange(
  45. Call->getBeginLoc(), Call->getArg(0)->getBeginLoc())));
  46. // Remove the ')'
  47. CheckResult->Hints.push_back(
  48. FixItHint::CreateRemoval(CharSourceRange::getCharRange(
  49. Call->getRParenLoc(), Call->getEndLoc().getLocWithOffset(1))));
  50. }
  51. const clang::CallExpr *processArgument(const Expr *Arg,
  52. const MatchFinder::MatchResult &Result,
  53. StrCatCheckResult *CheckResult) {
  54. const auto IsAlphanum = hasDeclaration(cxxMethodDecl(hasName("AlphaNum")));
  55. static const auto* const Strcat = new auto(hasName("::absl::StrCat"));
  56. const auto IsStrcat = cxxBindTemporaryExpr(
  57. has(callExpr(callee(functionDecl(*Strcat))).bind("StrCat")));
  58. if (const auto *SubStrcatCall = selectFirst<const CallExpr>(
  59. "StrCat",
  60. match(stmt(traverse(TK_AsIs,
  61. anyOf(cxxConstructExpr(IsAlphanum,
  62. hasArgument(0, IsStrcat)),
  63. IsStrcat))),
  64. *Arg->IgnoreParenImpCasts(), *Result.Context))) {
  65. removeCallLeaveArgs(SubStrcatCall, CheckResult);
  66. return SubStrcatCall;
  67. }
  68. return nullptr;
  69. }
  70. StrCatCheckResult processCall(const CallExpr *RootCall, bool IsAppend,
  71. const MatchFinder::MatchResult &Result) {
  72. StrCatCheckResult CheckResult;
  73. std::deque<const CallExpr*> CallsToProcess = {RootCall};
  74. while (!CallsToProcess.empty()) {
  75. ++CheckResult.NumCalls;
  76. const CallExpr* CallExpr = CallsToProcess.front();
  77. CallsToProcess.pop_front();
  78. int StartArg = CallExpr == RootCall && IsAppend;
  79. for (const auto *Arg : CallExpr->arguments()) {
  80. if (StartArg-- > 0)
  81. continue;
  82. if (const clang::CallExpr *Sub =
  83. processArgument(Arg, Result, &CheckResult)) {
  84. CallsToProcess.push_back(Sub);
  85. }
  86. }
  87. }
  88. return CheckResult;
  89. }
  90. } // namespace
  91. void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult& Result) {
  92. bool IsAppend;
  93. const CallExpr* RootCall;
  94. if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrCat")))
  95. IsAppend = false;
  96. else if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrAppend")))
  97. IsAppend = true;
  98. else
  99. return;
  100. if (RootCall->getBeginLoc().isMacroID()) {
  101. // Ignore calls within macros.
  102. // In many cases the outer StrCat part of the macro and the inner StrCat is
  103. // a macro argument. Removing the inner StrCat() converts one macro
  104. // argument into many.
  105. return;
  106. }
  107. const StrCatCheckResult CheckResult = processCall(RootCall, IsAppend, Result);
  108. if (CheckResult.NumCalls == 1) {
  109. // Just one call, so nothing to fix.
  110. return;
  111. }
  112. diag(RootCall->getBeginLoc(),
  113. "multiple calls to 'absl::StrCat' can be flattened into a single call")
  114. << CheckResult.Hints;
  115. }
  116. } // namespace clang::tidy::abseil