| //===--- RedundantStrcatCallsCheck.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 "RedundantStrcatCallsCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace abseil { |
| |
| // TODO: Features to add to the check: |
| // - Make it work if num_args > 26. |
| // - Remove empty literal string arguments. |
| // - Collapse consecutive literal string arguments into one (remove the ,). |
| // - Replace StrCat(a + b) -> StrCat(a, b) if a or b are strings. |
| // - Make it work in macros if the outer and inner StrCats are both in the |
| // argument. |
| |
| void RedundantStrcatCallsCheck::registerMatchers(MatchFinder* Finder) { |
| const auto CallToStrcat = |
| callExpr(callee(functionDecl(hasName("::absl::StrCat")))); |
| const auto CallToStrappend = |
| callExpr(callee(functionDecl(hasName("::absl::StrAppend")))); |
| // Do not match StrCat() calls that are descendants of other StrCat calls. |
| // Those are handled on the ancestor call. |
| const auto CallToEither = callExpr( |
| callee(functionDecl(hasAnyName("::absl::StrCat", "::absl::StrAppend")))); |
| Finder->addMatcher( |
| callExpr(CallToStrcat, unless(hasAncestor(CallToEither))).bind("StrCat"), |
| this); |
| Finder->addMatcher(CallToStrappend.bind("StrAppend"), this); |
| } |
| |
| namespace { |
| |
| struct StrCatCheckResult { |
| int NumCalls = 0; |
| std::vector<FixItHint> Hints; |
| }; |
| |
| void removeCallLeaveArgs(const CallExpr *Call, StrCatCheckResult *CheckResult) { |
| if (Call->getNumArgs() == 0) |
| return; |
| // Remove 'Foo(' |
| CheckResult->Hints.push_back( |
| FixItHint::CreateRemoval(CharSourceRange::getCharRange( |
| Call->getBeginLoc(), Call->getArg(0)->getBeginLoc()))); |
| // Remove the ')' |
| CheckResult->Hints.push_back( |
| FixItHint::CreateRemoval(CharSourceRange::getCharRange( |
| Call->getRParenLoc(), Call->getEndLoc().getLocWithOffset(1)))); |
| } |
| |
| const clang::CallExpr *processArgument(const Expr *Arg, |
| const MatchFinder::MatchResult &Result, |
| StrCatCheckResult *CheckResult) { |
| const auto IsAlphanum = hasDeclaration(cxxMethodDecl(hasName("AlphaNum"))); |
| static const auto* const Strcat = new auto(hasName("::absl::StrCat")); |
| const auto IsStrcat = cxxBindTemporaryExpr( |
| has(callExpr(callee(functionDecl(*Strcat))).bind("StrCat"))); |
| if (const auto *SubStrcatCall = selectFirst<const CallExpr>( |
| "StrCat", |
| match(stmt(traverse(TK_AsIs, |
| anyOf(cxxConstructExpr(IsAlphanum, |
| hasArgument(0, IsStrcat)), |
| IsStrcat))), |
| *Arg->IgnoreParenImpCasts(), *Result.Context))) { |
| removeCallLeaveArgs(SubStrcatCall, CheckResult); |
| return SubStrcatCall; |
| } |
| return nullptr; |
| } |
| |
| StrCatCheckResult processCall(const CallExpr *RootCall, bool IsAppend, |
| const MatchFinder::MatchResult &Result) { |
| StrCatCheckResult CheckResult; |
| std::deque<const CallExpr*> CallsToProcess = {RootCall}; |
| |
| while (!CallsToProcess.empty()) { |
| ++CheckResult.NumCalls; |
| |
| const CallExpr* CallExpr = CallsToProcess.front(); |
| CallsToProcess.pop_front(); |
| |
| int StartArg = CallExpr == RootCall && IsAppend; |
| for (const auto *Arg : CallExpr->arguments()) { |
| if (StartArg-- > 0) |
| continue; |
| if (const clang::CallExpr *Sub = |
| processArgument(Arg, Result, &CheckResult)) { |
| CallsToProcess.push_back(Sub); |
| } |
| } |
| } |
| return CheckResult; |
| } |
| } // namespace |
| |
| void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult& Result) { |
| bool IsAppend; |
| |
| const CallExpr* RootCall; |
| if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrCat"))) |
| IsAppend = false; |
| else if ((RootCall = Result.Nodes.getNodeAs<CallExpr>("StrAppend"))) |
| IsAppend = true; |
| else |
| return; |
| |
| if (RootCall->getBeginLoc().isMacroID()) { |
| // Ignore calls within macros. |
| // In many cases the outer StrCat part of the macro and the inner StrCat is |
| // a macro argument. Removing the inner StrCat() converts one macro |
| // argument into many. |
| return; |
| } |
| |
| const StrCatCheckResult CheckResult = processCall(RootCall, IsAppend, Result); |
| if (CheckResult.NumCalls == 1) { |
| // Just one call, so nothing to fix. |
| return; |
| } |
| |
| diag(RootCall->getBeginLoc(), |
| "multiple calls to 'absl::StrCat' can be flattened into a single call") |
| << CheckResult.Hints; |
| } |
| |
| } // namespace abseil |
| } // namespace tidy |
| } // namespace clang |