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