1 //===--- FasterStringFindCheck.cpp - clang-tidy----------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "FasterStringFindCheck.h" 10 #include "../utils/OptionsUtils.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "llvm/Support/raw_ostream.h" 14 #include <optional> 15 16 using namespace clang::ast_matchers; 17 18 namespace clang::tidy::performance { 19 20 namespace { 21 22 std::optional<std::string> makeCharacterLiteral(const StringLiteral *Literal) { 23 std::string Result; 24 { 25 llvm::raw_string_ostream OS(Result); 26 Literal->outputString(OS); 27 } 28 // Now replace the " with '. 29 auto Pos = Result.find_first_of('"'); 30 if (Pos == Result.npos) 31 return std::nullopt; 32 Result[Pos] = '\''; 33 Pos = Result.find_last_of('"'); 34 if (Pos == Result.npos) 35 return std::nullopt; 36 Result[Pos] = '\''; 37 return Result; 38 } 39 40 AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>, 41 hasSubstitutedType) { 42 return hasType(qualType(anyOf(substTemplateTypeParmType(), 43 hasDescendant(substTemplateTypeParmType())))); 44 } 45 46 } // namespace 47 48 FasterStringFindCheck::FasterStringFindCheck(StringRef Name, 49 ClangTidyContext *Context) 50 : ClangTidyCheck(Name, Context), 51 StringLikeClasses(utils::options::parseStringList( 52 Options.get("StringLikeClasses", 53 "::std::basic_string;::std::basic_string_view"))) {} 54 55 void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 56 Options.store(Opts, "StringLikeClasses", 57 utils::options::serializeStringList(StringLikeClasses)); 58 } 59 60 void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) { 61 const auto SingleChar = 62 expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"))); 63 const auto StringFindFunctions = 64 hasAnyName("find", "rfind", "find_first_of", "find_first_not_of", 65 "find_last_of", "find_last_not_of"); 66 67 Finder->addMatcher( 68 cxxMemberCallExpr( 69 callee(functionDecl(StringFindFunctions).bind("func")), 70 anyOf(argumentCountIs(1), argumentCountIs(2)), 71 hasArgument(0, SingleChar), 72 on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration( 73 recordDecl(hasAnyName(StringLikeClasses)))))), 74 unless(hasSubstitutedType())))), 75 this); 76 } 77 78 void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { 79 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal"); 80 const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func"); 81 82 auto Replacement = makeCharacterLiteral(Literal); 83 if (!Replacement) 84 return; 85 86 diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of " 87 "a single character; consider using the more " 88 "effective overload accepting a character") 89 << FindFunc 90 << FixItHint::CreateReplacement( 91 CharSourceRange::getTokenRange(Literal->getBeginLoc(), 92 Literal->getEndLoc()), 93 *Replacement); 94 } 95 96 } // namespace clang::tidy::performance 97