• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===--- FasterStrsplitDelimiterCheck.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 "FasterStrsplitDelimiterCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Tooling/FixIt.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace abseil {
19 
20 namespace {
21 
AST_MATCHER(StringLiteral,lengthIsOne)22 AST_MATCHER(StringLiteral, lengthIsOne) { return Node.getLength() == 1; }
23 
makeCharacterLiteral(const StringLiteral * Literal,const ASTContext & Context)24 llvm::Optional<std::string> makeCharacterLiteral(const StringLiteral *Literal,
25                                                  const ASTContext &Context) {
26   assert(Literal->getLength() == 1 &&
27          "Only single character string should be matched");
28   assert(Literal->getCharByteWidth() == 1 &&
29          "StrSplit doesn't support wide char");
30   std::string Result = clang::tooling::fixit::getText(*Literal, Context).str();
31   bool IsRawStringLiteral = StringRef(Result).startswith(R"(R")");
32   // Since raw string literal might contain unescaped non-printable characters,
33   // we normalize them using `StringLiteral::outputString`.
34   if (IsRawStringLiteral) {
35     Result.clear();
36     llvm::raw_string_ostream Stream(Result);
37     Literal->outputString(Stream);
38   }
39   // Special case: If the string contains a single quote, we just need to return
40   // a character of the single quote. This is a special case because we need to
41   // escape it in the character literal.
42   if (Result == R"("'")")
43     return std::string(R"('\'')");
44 
45   // Now replace the " with '.
46   std::string::size_type Pos = Result.find_first_of('"');
47   if (Pos == Result.npos)
48     return llvm::None;
49   Result[Pos] = '\'';
50   Pos = Result.find_last_of('"');
51   if (Pos == Result.npos)
52     return llvm::None;
53   Result[Pos] = '\'';
54   return Result;
55 }
56 
57 } // anonymous namespace
58 
registerMatchers(MatchFinder * Finder)59 void FasterStrsplitDelimiterCheck::registerMatchers(MatchFinder *Finder) {
60   // Binds to one character string literals.
61   const auto SingleChar =
62       expr(ignoringParenCasts(stringLiteral(lengthIsOne()).bind("Literal")));
63 
64   // Binds to a string_view (either absl or std) that was passed by value and
65   // constructed from string literal.
66   auto StringViewArg = ignoringElidableConstructorCall(ignoringImpCasts(
67       cxxConstructExpr(hasType(recordDecl(hasName("::absl::string_view"))),
68                        hasArgument(0, ignoringParenImpCasts(SingleChar)))));
69 
70   // Need to ignore the elidable constructor as otherwise there is no match for
71   // c++14 and earlier.
72   auto ByAnyCharArg =
73       expr(has(ignoringElidableConstructorCall(
74                ignoringParenCasts(cxxBindTemporaryExpr(has(cxxConstructExpr(
75                    hasType(recordDecl(hasName("::absl::ByAnyChar"))),
76                    hasArgument(0, StringViewArg))))))))
77           .bind("ByAnyChar");
78 
79   // Find uses of absl::StrSplit(..., "x") and absl::StrSplit(...,
80   // absl::ByAnyChar("x")) to transform them into absl::StrSplit(..., 'x').
81   Finder->addMatcher(
82       traverse(ast_type_traits::TK_AsIs,
83                callExpr(callee(functionDecl(hasName("::absl::StrSplit"))),
84                         hasArgument(1, anyOf(ByAnyCharArg, SingleChar)),
85                         unless(isInTemplateInstantiation()))
86                    .bind("StrSplit")),
87       this);
88 
89   // Find uses of absl::MaxSplits("x", N) and
90   // absl::MaxSplits(absl::ByAnyChar("x"), N) to transform them into
91   // absl::MaxSplits('x', N).
92   Finder->addMatcher(
93       traverse(ast_type_traits::TK_AsIs,
94                callExpr(callee(functionDecl(hasName("::absl::MaxSplits"))),
95                         hasArgument(0, anyOf(ByAnyCharArg,
96                                              ignoringParenCasts(SingleChar))),
97                         unless(isInTemplateInstantiation()))),
98       this);
99 }
100 
check(const MatchFinder::MatchResult & Result)101 void FasterStrsplitDelimiterCheck::check(
102     const MatchFinder::MatchResult &Result) {
103   const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("Literal");
104 
105   if (Literal->getBeginLoc().isMacroID() || Literal->getEndLoc().isMacroID())
106     return;
107 
108   llvm::Optional<std::string> Replacement =
109       makeCharacterLiteral(Literal, *Result.Context);
110   if (!Replacement)
111     return;
112   SourceRange Range = Literal->getSourceRange();
113 
114   if (const auto *ByAnyChar = Result.Nodes.getNodeAs<Expr>("ByAnyChar"))
115     Range = ByAnyChar->getSourceRange();
116 
117   diag(
118       Literal->getBeginLoc(),
119       "%select{absl::StrSplit()|absl::MaxSplits()}0 called with a string "
120       "literal "
121       "consisting of a single character; consider using the character overload")
122       << (Result.Nodes.getNodeAs<CallExpr>("StrSplit") ? 0 : 1)
123       << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(Range),
124                                       *Replacement);
125 }
126 
127 } // namespace abseil
128 } // namespace tidy
129 } // namespace clang
130