forked from OSchip/llvm-project
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. llvm-svn: 340918
This commit is contained in:
parent
09cc7af85a
commit
ca5f775dbe
|
@ -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>(
|
||||
|
|
|
@ -5,6 +5,7 @@ add_clang_library(clangTidyAbseilModule
|
|||
DurationDivisionCheck.cpp
|
||||
FasterStrsplitDelimiterCheck.cpp
|
||||
NoNamespaceCheck.cpp
|
||||
RedundantStrcatCallsCheck.cpp
|
||||
StringFindStartswithCheck.cpp
|
||||
StrCatAppendCheck.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
|
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
Loading…
Reference in New Issue