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 "../utils/OptionsUtils.h" 12 #include "clang/AST/ASTContext.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 #include "llvm/ADT/Optional.h" 15 #include "llvm/Support/raw_ostream.h" 16 17 using namespace clang::ast_matchers; 18 19 namespace clang { 20 namespace tidy { 21 namespace performance { 22 23 namespace { 24 25 llvm::Optional<std::string> MakeCharacterLiteral(const StringLiteral *Literal) { 26 std::string Result; 27 { 28 llvm::raw_string_ostream OS(Result); 29 Literal->outputString(OS); 30 } 31 // Now replace the " with '. 32 auto pos = Result.find_first_of('"'); 33 if (pos == Result.npos) return llvm::None; 34 Result[pos] = '\''; 35 pos = Result.find_last_of('"'); 36 if (pos == Result.npos) return llvm::None; 37 Result[pos] = '\''; 38 return Result; 39 } 40 41 AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; } 42 43 AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>, 44 hasSubstitutedType) { 45 return hasType(qualType(anyOf(substTemplateTypeParmType(), 46 hasDescendant(substTemplateTypeParmType())))); 47 } 48 49 } // namespace 50 51 FasterStringFindCheck::FasterStringFindCheck(StringRef Name, 52 ClangTidyContext *Context) 53 : ClangTidyCheck(Name, Context), 54 StringLikeClasses(utils::options::parseStringList( 55 Options.get("StringLikeClasses", "std::basic_string"))) { 56 } 57 58 void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 59 Options.store(Opts, "StringLikeClasses", 60 utils::options::serializeStringList(StringLikeClasses)); 61 } 62 63 void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) { 64 if (!getLangOpts().CPlusPlus) 65 return; 66 67 const auto SingleChar = 68 expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("literal"))); 69 70 const auto StringFindFunctions = 71 anyOf(hasName("find"), hasName("rfind"), hasName("find_first_of"), 72 hasName("find_first_not_of"), hasName("find_last_of"), 73 hasName("find_last_not_of")); 74 75 llvm::Optional<ast_matchers::internal::Matcher<NamedDecl>> IsStringClass; 76 77 for (const auto &ClassName : StringLikeClasses) { 78 const auto HasName = hasName(ClassName); 79 IsStringClass = IsStringClass ? anyOf(*IsStringClass, HasName) : HasName; 80 } 81 82 if (IsStringClass) { 83 Finder->addMatcher( 84 cxxMemberCallExpr( 85 callee(functionDecl(StringFindFunctions).bind("func")), 86 anyOf(argumentCountIs(1), argumentCountIs(2)), 87 hasArgument(0, SingleChar), 88 on(expr(hasType(recordDecl(*IsStringClass)), 89 unless(hasSubstitutedType())))), 90 this); 91 } 92 } 93 94 void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { 95 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal"); 96 const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func"); 97 98 auto Replacement = MakeCharacterLiteral(Literal); 99 if (!Replacement) 100 return; 101 102 diag(Literal->getLocStart(), "%0 called with a string literal consisting of " 103 "a single character; consider using the more " 104 "effective overload accepting a character") 105 << FindFunc << FixItHint::CreateReplacement( 106 CharSourceRange::getTokenRange(Literal->getLocStart(), 107 Literal->getLocEnd()), 108 *Replacement); 109 } 110 111 } // namespace performance 112 } // namespace tidy 113 } // namespace clang 114