diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp new file mode 100644 index 00000000000..19806685f89 --- /dev/null +++ b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp @@ -0,0 +1,140 @@ +//===--- RedundantStrcatCallsCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#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) { + if (!getLangOpts().CPlusPlus) + return; + 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) { + // 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(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 |