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 OpenPos = Result.find_first_of('"'); 30 if (OpenPos == Result.npos) 31 return std::nullopt; 32 Result[OpenPos] = '\''; 33 34 auto ClosePos = Result.find_last_of('"'); 35 if (ClosePos == Result.npos) 36 return std::nullopt; 37 Result[ClosePos] = '\''; 38 39 // "'" is OK, but ''' is not, so add a backslash 40 if ((ClosePos - OpenPos) == 2 && Result[OpenPos + 1] == '\'') 41 Result.replace(OpenPos + 1, 1, "\\'"); 42 43 return Result; 44 } 45 46 AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>, 47 hasSubstitutedType) { 48 return hasType(qualType(anyOf(substTemplateTypeParmType(), 49 hasDescendant(substTemplateTypeParmType())))); 50 } 51 52 } // namespace 53 54 FasterStringFindCheck::FasterStringFindCheck(StringRef Name, 55 ClangTidyContext *Context) 56 : ClangTidyCheck(Name, Context), 57 StringLikeClasses(utils::options::parseStringList( 58 Options.get("StringLikeClasses", 59 "::std::basic_string;::std::basic_string_view"))) {} 60 61 void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 62 Options.store(Opts, "StringLikeClasses", 63 utils::options::serializeStringList(StringLikeClasses)); 64 } 65 66 void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) { 67 const auto SingleChar = 68 expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"))); 69 const auto StringFindFunctions = 70 hasAnyName("find", "rfind", "find_first_of", "find_first_not_of", 71 "find_last_of", "find_last_not_of"); 72 73 Finder->addMatcher( 74 cxxMemberCallExpr( 75 callee(functionDecl(StringFindFunctions).bind("func")), 76 anyOf(argumentCountIs(1), argumentCountIs(2)), 77 hasArgument(0, SingleChar), 78 on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration( 79 recordDecl(hasAnyName(StringLikeClasses)))))), 80 unless(hasSubstitutedType())))), 81 this); 82 } 83 84 void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { 85 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal"); 86 const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func"); 87 88 auto Replacement = makeCharacterLiteral(Literal); 89 if (!Replacement) 90 return; 91 92 diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of " 93 "a single character; consider using the more " 94 "effective overload accepting a character") 95 << FindFunc 96 << FixItHint::CreateReplacement( 97 CharSourceRange::getTokenRange(Literal->getBeginLoc(), 98 Literal->getEndLoc()), 99 *Replacement); 100 } 101 102 } // namespace clang::tidy::performance 103