diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy')
4 files changed, 183 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp index e70184bfb3d..e7e0a2bdc3c 100644 --- a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp @@ -13,6 +13,7 @@ #include "DurationDivisionCheck.h" #include "FasterStrsplitDelimiterCheck.h" #include "NoNamespaceCheck.h" +#include "RedundantStrcatCallsCheck.h" #include "StringFindStartswithCheck.h" #include "StrCatAppendCheck.h" @@ -28,6 +29,8 @@ public: CheckFactories.registerCheck<FasterStrsplitDelimiterCheck>( "abseil-faster-strsplit-delimiter"); CheckFactories.registerCheck<NoNamespaceCheck>("abseil-no-namespace"); + CheckFactories.registerCheck<RedundantStrcatCallsCheck>( + "abseil-redundant-strcat-calls"); CheckFactories.registerCheck<StringFindStartswithCheck>( "abseil-string-find-startswith"); CheckFactories.registerCheck<StrCatAppendCheck>( diff --git a/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt b/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt index 97932013a6f..f36c0d979ec 100644 --- a/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/abseil/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_library(clangTidyAbseilModule DurationDivisionCheck.cpp FasterStrsplitDelimiterCheck.cpp NoNamespaceCheck.cpp + RedundantStrcatCallsCheck.cpp StringFindStartswithCheck.cpp StrCatAppendCheck.cpp 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 diff --git a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h new file mode 100644 index 00000000000..ede03547960 --- /dev/null +++ b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h @@ -0,0 +1,39 @@ +//===--- RedundantStrcatCallsCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace abseil { + +/// Flags redundant calls to absl::StrCat when the result is being passed to +/// another call of absl::StrCat/absl::StrAppend. Also suggests a fix to +/// collapse the calls. +/// Example: +/// StrCat(1, StrCat(2, 3)) ==> StrCat(1, 2, 3) +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/abseil-redundant-strcat-calls.html +class RedundantStrcatCallsCheck : public ClangTidyCheck { +public: + RedundantStrcatCallsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace abseil +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_REDUNDANTSTRCATCALLSCHECK_H |