1 //===--- FasterStringFindCheck.cpp - clang-tidy----------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "FasterStringFindCheck.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "llvm/ADT/Optional.h" 14 #include "llvm/Support/raw_ostream.h" 15 16 using namespace clang::ast_matchers; 17 18 namespace clang { 19 namespace tidy { 20 namespace performance { 21 22 namespace { 23 24 static const char StringLikeClassesDelimiter[] = ";"; 25 26 std::vector<std::string> ParseClasses(StringRef Option) { 27 SmallVector<StringRef, 4> Classes; 28 Option.split(Classes, StringLikeClassesDelimiter); 29 std::vector<std::string> Result; 30 for (StringRef &Class : Classes) { 31 Class = Class.trim(); 32 if (!Class.empty()) 33 Result.push_back(Class); 34 } 35 return Result; 36 } 37 38 llvm::Optional<std::string> MakeCharacterLiteral(const StringLiteral *Literal) { 39 std::string Result; 40 { 41 llvm::raw_string_ostream OS(Result); 42 Literal->outputString(OS); 43 } 44 // Now replace the " with '. 45 auto pos = Result.find_first_of('"'); 46 if (pos == Result.npos) return llvm::None; 47 Result[pos] = '\''; 48 pos = Result.find_last_of('"'); 49 if (pos == Result.npos) return llvm::None; 50 Result[pos] = '\''; 51 return Result; 52 } 53 54 AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; } 55 56 AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>, 57 hasSubstitutedType) { 58 return hasType(qualType(anyOf(substTemplateTypeParmType(), 59 hasDescendant(substTemplateTypeParmType())))); 60 } 61 62 } // namespace 63 64 FasterStringFindCheck::FasterStringFindCheck(StringRef Name, 65 ClangTidyContext *Context) 66 : ClangTidyCheck(Name, Context), 67 StringLikeClasses( 68 ParseClasses(Options.get("StringLikeClasses", "std::basic_string"))) { 69 } 70 71 void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 72 Options.store(Opts, "StringLikeClasses", 73 llvm::join(StringLikeClasses.begin(), StringLikeClasses.end(), 74 StringLikeClassesDelimiter)); 75 } 76 77 void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) { 78 if (!getLangOpts().CPlusPlus) 79 return; 80 81 const auto SingleChar = 82 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("literal"))); 83 84 const auto StringFindFunctions = 85 anyOf(hasName("find"), hasName("rfind"), hasName("find_first_of"), 86 hasName("find_first_not_of"), hasName("find_last_of"), 87 hasName("find_last_not_of")); 88 89 llvm::Optional<ast_matchers::internal::Matcher<NamedDecl>> IsStringClass; 90 91 for (const auto &ClassName : StringLikeClasses) { 92 const auto HasName = hasName(ClassName); 93 IsStringClass = IsStringClass ? anyOf(*IsStringClass, HasName) : HasName; 94 } 95 96 if (IsStringClass) { 97 Finder->addMatcher( 98 cxxMemberCallExpr( 99 callee(functionDecl(StringFindFunctions).bind("func")), 100 anyOf(argumentCountIs(1), argumentCountIs(2)), 101 hasArgument(0, SingleChar), 102 on(expr(hasType(recordDecl(*IsStringClass)), 103 unless(hasSubstitutedType())))), 104 this); 105 } 106 } 107 108 void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { 109 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal"); 110 const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func"); 111 112 auto Replacement = MakeCharacterLiteral(Literal); 113 if (!Replacement) 114 return; 115 116 diag(Literal->getLocStart(), "%0 called with a string literal consisting of " 117 "a single character; consider using the more " 118 "effective overload accepting a character") 119 << FindFunc->getName() 120 << FixItHint::CreateReplacement( 121 CharSourceRange::getTokenRange(Literal->getLocStart(), 122 Literal->getLocEnd()), 123 *Replacement); 124 } 125 126 } // namespace performance 127 } // namespace tidy 128 } // namespace clang 129