xref: /llvm-project/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp (revision 8ba103ca681670750e0929f74ab72870a871202f)
151e1523dSSamuel Benzaquen //===--- FasterStringFindCheck.cpp - clang-tidy----------------------------===//
251e1523dSSamuel Benzaquen //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
651e1523dSSamuel Benzaquen //
751e1523dSSamuel Benzaquen //===----------------------------------------------------------------------===//
851e1523dSSamuel Benzaquen 
951e1523dSSamuel Benzaquen #include "FasterStringFindCheck.h"
10de1ec037SEtienne Bergeron #include "../utils/OptionsUtils.h"
1151e1523dSSamuel Benzaquen #include "clang/AST/ASTContext.h"
1251e1523dSSamuel Benzaquen #include "clang/ASTMatchers/ASTMatchFinder.h"
1351e1523dSSamuel Benzaquen #include "llvm/Support/raw_ostream.h"
1471f55735SKazu Hirata #include <optional>
1551e1523dSSamuel Benzaquen 
1651e1523dSSamuel Benzaquen using namespace clang::ast_matchers;
1751e1523dSSamuel Benzaquen 
187d2ea6c4SCarlos Galvez namespace clang::tidy::performance {
1951e1523dSSamuel Benzaquen 
2051e1523dSSamuel Benzaquen namespace {
2151e1523dSSamuel Benzaquen 
makeCharacterLiteral(const StringLiteral * Literal)22f71ffd3bSKazu Hirata std::optional<std::string> makeCharacterLiteral(const StringLiteral *Literal) {
2351e1523dSSamuel Benzaquen   std::string Result;
2451e1523dSSamuel Benzaquen   {
2551e1523dSSamuel Benzaquen     llvm::raw_string_ostream OS(Result);
2651e1523dSSamuel Benzaquen     Literal->outputString(OS);
2751e1523dSSamuel Benzaquen   }
2851e1523dSSamuel Benzaquen   // Now replace the " with '.
295b95d17dSFabian Wolff   auto OpenPos = Result.find_first_of('"');
30*8ba103caSPiotr Zegar   if (OpenPos == std::string::npos)
31cd8702efSKazu Hirata     return std::nullopt;
325b95d17dSFabian Wolff   Result[OpenPos] = '\'';
335b95d17dSFabian Wolff 
345b95d17dSFabian Wolff   auto ClosePos = Result.find_last_of('"');
35*8ba103caSPiotr Zegar   if (ClosePos == std::string::npos)
36cd8702efSKazu Hirata     return std::nullopt;
375b95d17dSFabian Wolff   Result[ClosePos] = '\'';
385b95d17dSFabian Wolff 
395b95d17dSFabian Wolff   // "'" is OK, but ''' is not, so add a backslash
405b95d17dSFabian Wolff   if ((ClosePos - OpenPos) == 2 && Result[OpenPos + 1] == '\'')
415b95d17dSFabian Wolff     Result.replace(OpenPos + 1, 1, "\\'");
425b95d17dSFabian Wolff 
4351e1523dSSamuel Benzaquen   return Result;
4451e1523dSSamuel Benzaquen }
4551e1523dSSamuel Benzaquen 
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>,hasSubstitutedType)4651e1523dSSamuel Benzaquen AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Expr>,
4751e1523dSSamuel Benzaquen                      hasSubstitutedType) {
4851e1523dSSamuel Benzaquen   return hasType(qualType(anyOf(substTemplateTypeParmType(),
4951e1523dSSamuel Benzaquen                                 hasDescendant(substTemplateTypeParmType()))));
5051e1523dSSamuel Benzaquen }
5151e1523dSSamuel Benzaquen 
5251e1523dSSamuel Benzaquen } // namespace
5351e1523dSSamuel Benzaquen 
FasterStringFindCheck(StringRef Name,ClangTidyContext * Context)5451e1523dSSamuel Benzaquen FasterStringFindCheck::FasterStringFindCheck(StringRef Name,
5551e1523dSSamuel Benzaquen                                              ClangTidyContext *Context)
5651e1523dSSamuel Benzaquen     : ClangTidyCheck(Name, Context),
57de1ec037SEtienne Bergeron       StringLikeClasses(utils::options::parseStringList(
582efba0e8SNathan James           Options.get("StringLikeClasses",
592efba0e8SNathan James                       "::std::basic_string;::std::basic_string_view"))) {}
6051e1523dSSamuel Benzaquen 
storeOptions(ClangTidyOptions::OptionMap & Opts)6151e1523dSSamuel Benzaquen void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
6251e1523dSSamuel Benzaquen   Options.store(Opts, "StringLikeClasses",
63de1ec037SEtienne Bergeron                 utils::options::serializeStringList(StringLikeClasses));
6451e1523dSSamuel Benzaquen }
6551e1523dSSamuel Benzaquen 
registerMatchers(MatchFinder * Finder)6651e1523dSSamuel Benzaquen void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
6751e1523dSSamuel Benzaquen   const auto SingleChar =
68e15ef2f6SEtienne Bergeron       expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal")));
6951e1523dSSamuel Benzaquen   const auto StringFindFunctions =
708fd72968SHaojian Wu       hasAnyName("find", "rfind", "find_first_of", "find_first_not_of",
718fd72968SHaojian Wu                  "find_last_of", "find_last_not_of");
7251e1523dSSamuel Benzaquen 
7351e1523dSSamuel Benzaquen   Finder->addMatcher(
7451e1523dSSamuel Benzaquen       cxxMemberCallExpr(
7551e1523dSSamuel Benzaquen           callee(functionDecl(StringFindFunctions).bind("func")),
7651e1523dSSamuel Benzaquen           anyOf(argumentCountIs(1), argumentCountIs(2)),
7751e1523dSSamuel Benzaquen           hasArgument(0, SingleChar),
7812cb5405SNathan James           on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(
7912cb5405SNathan James                       recordDecl(hasAnyName(StringLikeClasses)))))),
8051e1523dSSamuel Benzaquen                   unless(hasSubstitutedType())))),
8151e1523dSSamuel Benzaquen       this);
8251e1523dSSamuel Benzaquen }
8351e1523dSSamuel Benzaquen 
check(const MatchFinder::MatchResult & Result)8451e1523dSSamuel Benzaquen void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
8551e1523dSSamuel Benzaquen   const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal");
8651e1523dSSamuel Benzaquen   const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func");
8751e1523dSSamuel Benzaquen 
88ab2d3ce4SAlexander Kornienko   auto Replacement = makeCharacterLiteral(Literal);
8951e1523dSSamuel Benzaquen   if (!Replacement)
9051e1523dSSamuel Benzaquen     return;
9151e1523dSSamuel Benzaquen 
9243465bf3SStephen Kelly   diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of "
9351e1523dSSamuel Benzaquen                                "a single character; consider using the more "
9451e1523dSSamuel Benzaquen                                "effective overload accepting a character")
9543465bf3SStephen Kelly       << FindFunc
9643465bf3SStephen Kelly       << FixItHint::CreateReplacement(
9743465bf3SStephen Kelly              CharSourceRange::getTokenRange(Literal->getBeginLoc(),
98c09197e0SStephen Kelly                                             Literal->getEndLoc()),
9951e1523dSSamuel Benzaquen              *Replacement);
10051e1523dSSamuel Benzaquen }
10151e1523dSSamuel Benzaquen 
1027d2ea6c4SCarlos Galvez } // namespace clang::tidy::performance
103