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/ADT/Optional.h" 14 #include "llvm/Support/raw_ostream.h" 15 #include <optional> 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) 34 return std::nullopt; 35 Result[Pos] = '\''; 36 Pos = Result.find_last_of('"'); 37 if (Pos == Result.npos) 38 return std::nullopt; 39 Result[Pos] = '\''; 40 return Result; 41 } 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", 56 "::std::basic_string;::std::basic_string_view"))) {} 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 const auto SingleChar = 65 expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"))); 66 const auto StringFindFunctions = 67 hasAnyName("find", "rfind", "find_first_of", "find_first_not_of", 68 "find_last_of", "find_last_not_of"); 69 70 Finder->addMatcher( 71 cxxMemberCallExpr( 72 callee(functionDecl(StringFindFunctions).bind("func")), 73 anyOf(argumentCountIs(1), argumentCountIs(2)), 74 hasArgument(0, SingleChar), 75 on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration( 76 recordDecl(hasAnyName(StringLikeClasses)))))), 77 unless(hasSubstitutedType())))), 78 this); 79 } 80 81 void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { 82 const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal"); 83 const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func"); 84 85 auto Replacement = makeCharacterLiteral(Literal); 86 if (!Replacement) 87 return; 88 89 diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of " 90 "a single character; consider using the more " 91 "effective overload accepting a character") 92 << FindFunc 93 << FixItHint::CreateReplacement( 94 CharSourceRange::getTokenRange(Literal->getBeginLoc(), 95 Literal->getEndLoc()), 96 *Replacement); 97 } 98 99 } // namespace performance 100 } // namespace tidy 101 } // namespace clang 102