Skip to content

Commit

Permalink
[clang-tidy] Add check performance-faster-string-find
Browse files Browse the repository at this point in the history
Summary:
Add check performance-faster-string-find.
It replaces single character string literals to character literals in calls to string::find and friends.

Reviewers: alexfh

Subscribers: cfe-commits

Differential Revision: http://reviews.llvm.org/D16152

llvm-svn: 260712
  • Loading branch information
sbenzaquen committed Feb 12, 2016
1 parent 96fccc2 commit 51e1523
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 0 deletions.
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/performance/CMakeLists.txt
@@ -1,6 +1,7 @@
set(LLVM_LINK_COMPONENTS support)

add_clang_library(clangTidyPerformanceModule
FasterStringFindCheck.cpp
ForRangeCopyCheck.cpp
ImplicitCastInLoopCheck.cpp
PerformanceTidyModule.cpp
Expand Down
128 changes: 128 additions & 0 deletions clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -0,0 +1,128 @@
//===--- FasterStringFindCheck.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 "FasterStringFindCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace performance {

namespace {

static const char StringLikeClassesDelimiter[] = ";";

std::vector<std::string> ParseClasses(StringRef Option) {
SmallVector<StringRef, 4> Classes;
Option.split(Classes, StringLikeClassesDelimiter);
std::vector<std::string> Result;
for (StringRef &Class : Classes) {
Class = Class.trim();
if (!Class.empty())
Result.push_back(Class);
}
return Result;
}

llvm::Optional<std::string> MakeCharacterLiteral(const StringLiteral *Literal) {
std::string Result;
{
llvm::raw_string_ostream OS(Result);
Literal->outputString(OS);
}
// Now replace the " with '.
auto pos = Result.find_first_of('"');
if (pos == Result.npos) return llvm::None;
Result[pos] = '\'';
pos = Result.find_last_of('"');
if (pos == Result.npos) return llvm::None;
Result[pos] = '\'';
return Result;
}

AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }

AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>,
hasSubstitutedType) {
return hasType(qualType(anyOf(substTemplateTypeParmType(),
hasDescendant(substTemplateTypeParmType()))));
}

} // namespace

FasterStringFindCheck::FasterStringFindCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StringLikeClasses(
ParseClasses(Options.get("StringLikeClasses", "std::basic_string"))) {
}

void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "StringLikeClasses",
llvm::join(StringLikeClasses.begin(), StringLikeClasses.end(),
StringLikeClassesDelimiter));
}

void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus)
return;

const auto SingleChar =
expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("literal")));

const auto StringFindFunctions =
anyOf(hasName("find"), hasName("rfind"), hasName("find_first_of"),
hasName("find_first_not_of"), hasName("find_last_of"),
hasName("find_last_not_of"));

llvm::Optional<ast_matchers::internal::Matcher<NamedDecl>> IsStringClass;

for (const auto &ClassName : StringLikeClasses) {
const auto HasName = hasName(ClassName);
IsStringClass = IsStringClass ? anyOf(*IsStringClass, HasName) : HasName;
}

if (IsStringClass) {
Finder->addMatcher(
cxxMemberCallExpr(
callee(functionDecl(StringFindFunctions).bind("func")),
anyOf(argumentCountIs(1), argumentCountIs(2)),
hasArgument(0, SingleChar),
on(expr(hasType(recordDecl(*IsStringClass)),
unless(hasSubstitutedType())))),
this);
}
}

void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal");
const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func");

auto Replacement = MakeCharacterLiteral(Literal);
if (!Replacement)
return;

diag(Literal->getLocStart(), "%0 called with a string literal consisting of "
"a single character; consider using the more "
"effective overload accepting a character")
<< FindFunc->getName()
<< FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(Literal->getLocStart(),
Literal->getLocEnd()),
*Replacement);
}

} // namespace performance
} // namespace tidy
} // namespace clang
43 changes: 43 additions & 0 deletions clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.h
@@ -0,0 +1,43 @@
//===--- FasterStringFindCheck.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_PERFORMANCE_FASTER_STRING_FIND_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H

#include "../ClangTidy.h"

#include <string>
#include <vector>

namespace clang {
namespace tidy {
namespace performance {

/// Optimize calls to std::string::find() and friends when the needle passed is
/// a single character string literal.
/// The character literal overload is more efficient.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/performance-faster-string-find.html
class FasterStringFindCheck : public ClangTidyCheck {
public:
FasterStringFindCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;

private:
const std::vector<std::string> StringLikeClasses;
};

} // namespace performance
} // namespace tidy
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H
Expand Up @@ -11,6 +11,7 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"

#include "FasterStringFindCheck.h"
#include "ForRangeCopyCheck.h"
#include "ImplicitCastInLoopCheck.h"
#include "UnnecessaryCopyInitialization.h"
Expand All @@ -22,6 +23,8 @@ namespace performance {
class PerformanceModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<FasterStringFindCheck>(
"performance-faster-string-find");
CheckFactories.registerCheck<ForRangeCopyCheck>(
"performance-for-range-copy");
CheckFactories.registerCheck<ImplicitCastInLoopCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Expand Up @@ -80,6 +80,7 @@ Clang-Tidy Checks
modernize-use-default
modernize-use-nullptr
modernize-use-override
performance-faster-string-find
performance-for-range-copy
performance-implicit-cast-in-loop
readability-braces-around-statements
Expand Down
@@ -0,0 +1,22 @@
.. title:: clang-tidy - performance-faster-string-find

performance-faster-string-find
==============================

Optimize calls to std::string::find() and friends when the needle passed is
a single character string literal.
The character literal overload is more efficient.

By default only `std::basic_string` is considered. This list can be modified by
passing a `;` separated list of class names using the `StringLikeClasses`
option. The methods to consired are fixed, though.

Examples:

.. code-block:: c++

str.find("A");

// becomes

str.find('A');
110 changes: 110 additions & 0 deletions clang-tools-extra/test/clang-tidy/performance-faster-string-find.cpp
@@ -0,0 +1,110 @@
// RUN: %check_clang_tidy %s performance-faster-string-find %t -- \
// RUN: -config="{CheckOptions: \
// RUN: [{key: performance-faster-string-find.StringLikeClasses, \
// RUN: value: 'std::basic_string; ::llvm::StringRef;'}]}" --

namespace std {
template <typename Char>
struct basic_string {
int find(const Char *, int = 0) const;
int find(const Char *, int, int) const;
int rfind(const Char *) const;
int find_first_of(const Char *) const;
int find_first_not_of(const Char *) const;
int find_last_of(const Char *) const;
int find_last_not_of(const Char *) const;
};

typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
} // namespace std

namespace llvm {
struct StringRef {
int find(const char *) const;
};
} // namespace llvm

struct NotStringRef {
int find(const char *);
};

void StringFind() {
std::string Str;

Str.find("a");
// CHECK-MESSAGES: [[@LINE-1]]:12: warning: find called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
// CHECK-FIXES: Str.find('a');

// Works with the pos argument.
Str.find("a", 1);
// CHECK-MESSAGES: [[@LINE-1]]:12: warning: find called with a string literal
// CHECK-FIXES: Str.find('a', 1);

// Doens't work with strings smaller or larger than 1 char.
Str.find("");
Str.find("ab");

// Doesn't do anything with the 3 argument overload.
Str.find("a", 1, 1);

// Other methods that can also be replaced
Str.rfind("a");
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: rfind called with a string literal
// CHECK-FIXES: Str.rfind('a');
Str.find_first_of("a");
// CHECK-MESSAGES: [[@LINE-1]]:21: warning: find_first_of called with a string
// CHECK-FIXES: Str.find_first_of('a');
Str.find_first_not_of("a");
// CHECK-MESSAGES: [[@LINE-1]]:25: warning: find_first_not_of called with a
// CHECK-FIXES: Str.find_first_not_of('a');
Str.find_last_of("a");
// CHECK-MESSAGES: [[@LINE-1]]:20: warning: find_last_of called with a string
// CHECK-FIXES: Str.find_last_of('a');
Str.find_last_not_of("a");
// CHECK-MESSAGES: [[@LINE-1]]:24: warning: find_last_not_of called with a
// CHECK-FIXES: Str.find_last_not_of('a');

// std::wstring should work.
std::wstring WStr;
WStr.find(L"n");
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: find called with a string literal
// CHECK-FIXES: Str.find(L'n');
// Even with unicode that fits in one wide char.
WStr.find(L"\x3A9");
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: find called with a string literal
// CHECK-FIXES: Str.find(L'\x3A9');

// Also with other types, but only if it was specified in the options.
llvm::StringRef sr;
sr.find("x");
// CHECK-MESSAGES: [[@LINE-1]]:11: warning: find called with a string literal
// CHECK-FIXES: sr.find('x');
NotStringRef nsr;
nsr.find("x");
}


template <typename T>
int FindTemplateDependant(T value) {
return value.find("A");
}
template <typename T>
int FindTemplateNotDependant(T pos) {
return std::string().find("A", pos);
// CHECK-MESSAGES: [[@LINE-1]]:29: warning: find called with a string literal
// CHECK-FIXES: return std::string().find('A', pos);
}

int FindStr() {
return FindTemplateDependant(std::string()) + FindTemplateNotDependant(1);
}

#define STR_MACRO(str) str.find("A")
#define POS_MACRO(pos) std::string().find("A",pos)

int Macros() {
return STR_MACRO(std::string()) + POS_MACRO(1);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: find called with a string literal
// CHECK-MESSAGES: [[@LINE-2]]:37: warning: find called with a string literal
}

0 comments on commit 51e1523

Please sign in to comment.