brintos

brintos / llvm-project-archived public Read only

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