[clang-tools-extra] r340918 - Introduce the abseil-redundant-strcat-calls check.

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 29 04:29:07 PDT 2018


Author: aaronballman
Date: Wed Aug 29 04:29:07 2018
New Revision: 340918

URL: http://llvm.org/viewvc/llvm-project?rev=340918&view=rev
Log:
Introduce the abseil-redundant-strcat-calls check.

This flags redundant calls to absl::StrCat where the result is being passed to another call to absl::StrCat or absl::StrAppend. Patch by Hugo Gonzalez and Samuel Benzaquen.

Added:
    clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp
    clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.h
    clang-tools-extra/trunk/docs/clang-tidy/checks/abseil-redundant-strcat-calls.rst
    clang-tools-extra/trunk/test/clang-tidy/abseil-redundant-strcat-calls.cpp
Modified:
    clang-tools-extra/trunk/clang-tidy/abseil/AbseilTidyModule.cpp
    clang-tools-extra/trunk/clang-tidy/abseil/CMakeLists.txt
    clang-tools-extra/trunk/docs/ReleaseNotes.rst
    clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst

Modified: clang-tools-extra/trunk/clang-tidy/abseil/AbseilTidyModule.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/abseil/AbseilTidyModule.cpp?rev=340918&r1=340917&r2=340918&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/abseil/AbseilTidyModule.cpp (original)
+++ clang-tools-extra/trunk/clang-tidy/abseil/AbseilTidyModule.cpp Wed Aug 29 04:29:07 2018
@@ -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>(

Modified: clang-tools-extra/trunk/clang-tidy/abseil/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/abseil/CMakeLists.txt?rev=340918&r1=340917&r2=340918&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/abseil/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-tidy/abseil/CMakeLists.txt Wed Aug 29 04:29:07 2018
@@ -5,6 +5,7 @@ add_clang_library(clangTidyAbseilModule
   DurationDivisionCheck.cpp
   FasterStrsplitDelimiterCheck.cpp
   NoNamespaceCheck.cpp
+  RedundantStrcatCallsCheck.cpp
   StringFindStartswithCheck.cpp
   StrCatAppendCheck.cpp
 

Added: clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp?rev=340918&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp (added)
+++ clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp Wed Aug 29 04:29:07 2018
@@ -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

Added: clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.h?rev=340918&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.h (added)
+++ clang-tools-extra/trunk/clang-tidy/abseil/RedundantStrcatCallsCheck.h Wed Aug 29 04:29:07 2018
@@ -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

Modified: clang-tools-extra/trunk/docs/ReleaseNotes.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/ReleaseNotes.rst?rev=340918&r1=340917&r2=340918&view=diff
==============================================================================
--- clang-tools-extra/trunk/docs/ReleaseNotes.rst (original)
+++ clang-tools-extra/trunk/docs/ReleaseNotes.rst Wed Aug 29 04:29:07 2018
@@ -76,6 +76,12 @@ Improvements to clang-tidy
   Ensures code does not open ``namespace absl`` as that violates Abseil's
   compatibility guidelines.
 
+- New :doc:`abseil-redundant-strcat-calls
+  <clang-tidy/checks/abseil-redundant-strcat-calls>` check.
+
+  Suggests removal of unnecessary calls to ``absl::StrCat`` when the result is
+  being passed to another ``absl::StrCat`` or ``absl::StrAppend``.
+
 - New :doc:`abseil-str-cat-append
   <clang-tidy/checks/abseil-str-cat-append>` check.
 

Added: clang-tools-extra/trunk/docs/clang-tidy/checks/abseil-redundant-strcat-calls.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/abseil-redundant-strcat-calls.rst?rev=340918&view=auto
==============================================================================
--- clang-tools-extra/trunk/docs/clang-tidy/checks/abseil-redundant-strcat-calls.rst (added)
+++ clang-tools-extra/trunk/docs/clang-tidy/checks/abseil-redundant-strcat-calls.rst Wed Aug 29 04:29:07 2018
@@ -0,0 +1,26 @@
+.. title:: clang-tidy - abseil-redundant-strcat-calls
+
+abseil-redundant-strcat-calls
+=============================
+
+Suggests removal of unnecessary calls to ``absl::StrCat`` when the result is 
+being passed to another call to ``absl::StrCat`` or ``absl::StrAppend``.
+
+The extra calls cause unnecessary temporary strings to be constructed. Removing
+them makes the code smaller and faster.
+
+Examples:
+
+.. code-block:: c++
+
+  std::string s = absl::StrCat("A", absl::StrCat("B", absl::StrCat("C", "D")));
+  //before
+
+  std::string s = absl::StrCat("A", "B", "C", "D");
+  //after
+
+  absl::StrAppend(&s, absl::StrCat("E", "F", "G"));
+  //before
+  
+  absl::StrAppend(&s, "E", "F", "G");
+  //after

Modified: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst?rev=340918&r1=340917&r2=340918&view=diff
==============================================================================
--- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst (original)
+++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Wed Aug 29 04:29:07 2018
@@ -7,6 +7,7 @@ Clang-Tidy Checks
    abseil-duration-division
    abseil-faster-strsplit-delimiter
    abseil-no-namespace
+   abseil-redundant-strcat-calls
    abseil-string-find-startswith
    abseil-str-cat-append
    android-cloexec-accept

Added: clang-tools-extra/trunk/test/clang-tidy/abseil-redundant-strcat-calls.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/abseil-redundant-strcat-calls.cpp?rev=340918&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clang-tidy/abseil-redundant-strcat-calls.cpp (added)
+++ clang-tools-extra/trunk/test/clang-tidy/abseil-redundant-strcat-calls.cpp Wed Aug 29 04:29:07 2018
@@ -0,0 +1,188 @@
+// RUN: %check_clang_tidy %s abseil-redundant-strcat-calls %t
+
+int strlen(const char *);
+
+// Here we mimic the hierarchy of ::string.
+// We need to do so because we are matching on the fully qualified name of the
+// methods.
+struct __sso_string_base {};
+namespace __gnu_cxx {
+template <typename A, typename B, typename C, typename D = __sso_string_base>
+class __versa_string {
+ public:
+  const char *c_str() const;
+  const char *data() const;
+  int size() const;
+  int capacity() const;
+  int length() const;
+  bool empty() const;
+  char &operator[](int);
+  void clear();
+  void resize(int);
+  int compare(const __versa_string &) const;
+};
+}  // namespace __gnu_cxx
+
+namespace std {
+template <typename T>
+class char_traits {};
+template <typename T>
+class allocator {};
+}  // namespace std
+
+template <typename A, typename B = std::char_traits<A>,
+          typename C = std::allocator<A>>
+class basic_string : public __gnu_cxx::__versa_string<A, B, C> {
+ public:
+  basic_string();
+  basic_string(const basic_string &);
+  basic_string(const char *, C = C());
+  basic_string(const char *, int, C = C());
+  basic_string(const basic_string &, int, int, C = C());
+  ~basic_string();
+
+  basic_string &operator+=(const basic_string &);
+};
+
+template <typename A, typename B, typename C>
+basic_string<A, B, C> operator+(const basic_string<A, B, C> &,
+                                const basic_string<A, B, C> &);
+template <typename A, typename B, typename C>
+basic_string<A, B, C> operator+(const basic_string<A, B, C> &, const char *);
+
+typedef basic_string<char> string;
+
+bool operator==(const string &, const string &);
+bool operator==(const string &, const char *);
+bool operator==(const char *, const string &);
+
+bool operator!=(const string &, const string &);
+bool operator<(const string &, const string &);
+bool operator>(const string &, const string &);
+bool operator<=(const string &, const string &);
+bool operator>=(const string &, const string &);
+
+namespace std {
+template <typename _CharT, typename _Traits = char_traits<_CharT>,
+          typename _Alloc = allocator<_CharT>>
+class basic_string;
+
+template <typename _CharT, typename _Traits, typename _Alloc>
+class basic_string {
+ public:
+  basic_string();
+  basic_string(const basic_string &);
+  basic_string(const char *, const _Alloc & = _Alloc());
+  basic_string(const char *, int, const _Alloc & = _Alloc());
+  basic_string(const basic_string &, int, int, const _Alloc & = _Alloc());
+  ~basic_string();
+
+  basic_string &operator+=(const basic_string &);
+
+  unsigned size() const;
+  unsigned length() const;
+  bool empty() const;
+};
+
+typedef basic_string<char> string;
+}  // namespace std
+
+namespace absl {
+
+class string_view {
+ public:
+  typedef std::char_traits<char> traits_type;
+
+  string_view();
+  string_view(const char *);
+  string_view(const string &);
+  string_view(const char *, int);
+  string_view(string_view, int);
+
+  template <typename A>
+  explicit operator ::basic_string<char, traits_type, A>() const;
+
+  const char *data() const;
+  int size() const;
+  int length() const;
+};
+
+bool operator==(string_view A, string_view B);
+
+struct AlphaNum {
+  AlphaNum(int i);
+  AlphaNum(double f);
+  AlphaNum(const char *c_str);
+  AlphaNum(const string &str);
+  AlphaNum(const string_view &pc);
+
+ private:
+  AlphaNum(const AlphaNum &);
+  AlphaNum &operator=(const AlphaNum &);
+};
+
+string StrCat(const AlphaNum &A);
+string StrCat(const AlphaNum &A, const AlphaNum &B);
+string StrCat(const AlphaNum &A, const AlphaNum &B, const AlphaNum &C);
+string StrCat(const AlphaNum &A, const AlphaNum &B, const AlphaNum &C,
+              const AlphaNum &D);
+
+// Support 5 or more arguments
+template <typename... AV>
+string StrCat(const AlphaNum &A, const AlphaNum &B, const AlphaNum &C,
+              const AlphaNum &D, const AlphaNum &E, const AV &... args);
+
+void StrAppend(string *Dest, const AlphaNum &A);
+void StrAppend(string *Dest, const AlphaNum &A, const AlphaNum &B);
+void StrAppend(string *Dest, const AlphaNum &A, const AlphaNum &B,
+               const AlphaNum &C);
+void StrAppend(string *Dest, const AlphaNum &A, const AlphaNum &B,
+               const AlphaNum &C, const AlphaNum &D);
+
+// Support 5 or more arguments
+template <typename... AV>
+void StrAppend(string *Dest, const AlphaNum &A, const AlphaNum &B,
+               const AlphaNum &C, const AlphaNum &D, const AlphaNum &E,
+               const AV &... args);
+
+}  // namespace absl
+
+using absl::AlphaNum;
+using absl::StrAppend;
+using absl::StrCat;
+
+void Positives() {
+  string S = StrCat(1, StrCat("A", StrCat(1.1)));
+  // CHECK-MESSAGES: [[@LINE-1]]:14: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+
+  S = StrCat(StrCat(StrCat(StrCat(StrCat(1)))));
+  // CHECK-MESSAGES: [[@LINE-1]]:7: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+
+  // TODO: should trigger. The issue here is that in the current
+  // implementation we ignore any StrCat with StrCat ancestors. Therefore
+  // inserting anything in between calls will disable triggering the deepest
+  // ones.
+  // s = StrCat(Identity(StrCat(StrCat(1, 2), StrCat(3, 4))));
+
+  StrAppend(&S, 001, StrCat(1, 2, "3"), StrCat("FOO"));
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+
+  StrAppend(&S, 001, StrCat(StrCat(1, 2), "3"), StrCat("FOO"));
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+
+  // Too many args. Ignore for now.
+  S = StrCat(1, 2, StrCat(3, 4, 5, 6, 7), 8, 9, 10,
+             StrCat(11, 12, 13, 14, 15, 16, 17, 18), 19, 20, 21, 22, 23, 24, 25,
+             26, 27);
+  // CHECK-MESSAGES: :[[@LINE-3]]:7: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+  StrAppend(&S, StrCat(1, 2, 3, 4, 5), StrCat(6, 7, 8, 9, 10));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: multiple calls to 'absl::StrCat' can be flattened into a single call
+}
+
+void Negatives() {
+  // One arg. It is used for conversion. Ignore.
+  string S = StrCat(1);
+
+#define A_MACRO(x, y, z) StrCat(x, y, z)
+  S = A_MACRO(1, 2, StrCat("A", "B"));
+}




More information about the cfe-commits mailing list