brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.0 KiB · 269996d Raw
334 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 "OperatorsRepresentationCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Lex/Lexer.h"14#include "llvm/ADT/STLExtras.h"15#include <array>16#include <utility>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::readability {21 22static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {23  if (Loc.isInvalid())24    return {};25 26  const SourceManager &SM = Context.getSourceManager();27 28  Loc = SM.getSpellingLoc(Loc);29  if (Loc.isInvalid())30    return {};31 32  const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);33  return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());34}35 36namespace {37 38AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,39               BinaryOperatorKind, Kind, llvm::StringRef,40               ExpectedRepresentation) {41  if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())42    return false;43 44  const StringRef Spelling =45      getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());46  return !Spelling.empty() && Spelling != ExpectedRepresentation;47}48 49AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,50               UnaryOperatorKind, Kind, llvm::StringRef,51               ExpectedRepresentation) {52  if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())53    return false;54 55  const StringRef Spelling =56      getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());57  return !Spelling.empty() && Spelling != ExpectedRepresentation;58}59 60AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,61               OverloadedOperatorKind, Kind, llvm::StringRef,62               ExpectedRepresentation) {63  if (Node.getOperator() != Kind || ExpectedRepresentation.empty())64    return false;65 66  const StringRef Spelling =67      getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());68  return !Spelling.empty() && Spelling != ExpectedRepresentation;69}70 71} // namespace72 73constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>74    UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};75 76constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>77    OperatorsRepresentation{{{"&&", "and"},78                             {"||", "or"},79                             {"^", "xor"},80                             {"&", "bitand"},81                             {"|", "bitor"},82                             {"&=", "and_eq"},83                             {"|=", "or_eq"},84                             {"!=", "not_eq"},85                             {"^=", "xor_eq"}}};86 87static llvm::StringRef translate(llvm::StringRef Value) {88  for (const auto &[Traditional, Alternative] : UnaryRepresentation) {89    if (Value == Traditional)90      return Alternative;91    if (Value == Alternative)92      return Traditional;93  }94 95  for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {96    if (Value == Traditional)97      return Alternative;98    if (Value == Alternative)99      return Traditional;100  }101  return {};102}103 104static bool isNotOperatorStr(llvm::StringRef Value) {105  return translate(Value).empty();106}107 108static bool isSeparator(char C) noexcept {109  constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");110  return Separators.contains(C);111}112 113static bool needEscaping(llvm::StringRef Operator) {114  switch (Operator[0]) {115  case '&':116  case '|':117  case '!':118  case '^':119  case '~':120    return false;121  default:122    return true;123  }124}125 126static llvm::StringRef127getRepresentation(const std::vector<llvm::StringRef> &Config,128                  llvm::StringRef Traditional, llvm::StringRef Alternative) {129  if (llvm::is_contained(Config, Traditional))130    return Traditional;131  if (llvm::is_contained(Config, Alternative))132    return Alternative;133  return {};134}135 136template <typename T>137static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,138                                 const T &Operators) {139  for (const auto &[traditional, alternative] : Operators) {140    if (!getRepresentation(Config, traditional, alternative).empty())141      return true;142  }143  return false;144}145 146OperatorsRepresentationCheck::OperatorsRepresentationCheck(147    StringRef Name, ClangTidyContext *Context)148    : ClangTidyCheck(Name, Context),149      BinaryOperators(150          utils::options::parseStringList(Options.get("BinaryOperators", ""))),151      OverloadedOperators(utils::options::parseStringList(152          Options.get("OverloadedOperators", ""))) {153  llvm::erase_if(BinaryOperators, isNotOperatorStr);154  llvm::erase_if(OverloadedOperators, isNotOperatorStr);155}156 157void OperatorsRepresentationCheck::storeOptions(158    ClangTidyOptions::OptionMap &Opts) {159  Options.store(Opts, "BinaryOperators",160                utils::options::serializeStringList(BinaryOperators));161  Options.store(Opts, "OverloadedOperators",162                utils::options::serializeStringList(OverloadedOperators));163}164 165std::optional<TraversalKind>166OperatorsRepresentationCheck::getCheckTraversalKind() const {167  return TK_IgnoreUnlessSpelledInSource;168}169 170bool OperatorsRepresentationCheck::isLanguageVersionSupported(171    const LangOptions &LangOpts) const {172  return LangOpts.CPlusPlus;173}174 175void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(176    MatchFinder *Finder) {177  if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))178    return;179 180  Finder->addMatcher(181      binaryOperator(182          unless(isExpansionInSystemHeader()),183          anyOf(hasInvalidBinaryOperatorRepresentation(184                    BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),185                hasInvalidBinaryOperatorRepresentation(186                    BO_LOr, getRepresentation(BinaryOperators, "||", "or")),187                hasInvalidBinaryOperatorRepresentation(188                    BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),189                hasInvalidBinaryOperatorRepresentation(190                    BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),191                hasInvalidBinaryOperatorRepresentation(192                    BO_And, getRepresentation(BinaryOperators, "&", "bitand")),193                hasInvalidBinaryOperatorRepresentation(194                    BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),195                hasInvalidBinaryOperatorRepresentation(196                    BO_AndAssign,197                    getRepresentation(BinaryOperators, "&=", "and_eq")),198                hasInvalidBinaryOperatorRepresentation(199                    BO_OrAssign,200                    getRepresentation(BinaryOperators, "|=", "or_eq")),201                hasInvalidBinaryOperatorRepresentation(202                    BO_XorAssign,203                    getRepresentation(BinaryOperators, "^=", "xor_eq"))))204          .bind("binary_op"),205      this);206}207 208void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(209    MatchFinder *Finder) {210  if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))211    return;212 213  Finder->addMatcher(214      unaryOperator(215          unless(isExpansionInSystemHeader()),216          anyOf(hasInvalidUnaryOperatorRepresentation(217                    UO_LNot, getRepresentation(BinaryOperators, "!", "not")),218                hasInvalidUnaryOperatorRepresentation(219                    UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))220          .bind("unary_op"),221      this);222}223 224void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(225    MatchFinder *Finder) {226  if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&227      !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))228    return;229 230  Finder->addMatcher(231      cxxOperatorCallExpr(232          unless(isExpansionInSystemHeader()),233          anyOf(234              hasInvalidOverloadedOperatorRepresentation(235                  OO_AmpAmp,236                  getRepresentation(OverloadedOperators, "&&", "and")),237              hasInvalidOverloadedOperatorRepresentation(238                  OO_PipePipe,239                  getRepresentation(OverloadedOperators, "||", "or")),240              hasInvalidOverloadedOperatorRepresentation(241                  OO_Exclaim,242                  getRepresentation(OverloadedOperators, "!", "not")),243              hasInvalidOverloadedOperatorRepresentation(244                  OO_ExclaimEqual,245                  getRepresentation(OverloadedOperators, "!=", "not_eq")),246              hasInvalidOverloadedOperatorRepresentation(247                  OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),248              hasInvalidOverloadedOperatorRepresentation(249                  OO_Amp,250                  getRepresentation(OverloadedOperators, "&", "bitand")),251              hasInvalidOverloadedOperatorRepresentation(252                  OO_Pipe,253                  getRepresentation(OverloadedOperators, "|", "bitor")),254              hasInvalidOverloadedOperatorRepresentation(255                  OO_AmpEqual,256                  getRepresentation(OverloadedOperators, "&=", "and_eq")),257              hasInvalidOverloadedOperatorRepresentation(258                  OO_PipeEqual,259                  getRepresentation(OverloadedOperators, "|=", "or_eq")),260              hasInvalidOverloadedOperatorRepresentation(261                  OO_CaretEqual,262                  getRepresentation(OverloadedOperators, "^=", "xor_eq")),263              hasInvalidOverloadedOperatorRepresentation(264                  OO_Tilde,265                  getRepresentation(OverloadedOperators, "~", "compl"))))266          .bind("overloaded_op"),267      this);268}269 270void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {271  registerBinaryOperatorMatcher(Finder);272  registerUnaryOperatorMatcher(Finder);273  registerOverloadedOperatorMatcher(Finder);274}275 276void OperatorsRepresentationCheck::check(277    const MatchFinder::MatchResult &Result) {278  SourceLocation Loc;279 280  if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))281    Loc = Op->getOperatorLoc();282  else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))283    Loc = Op->getOperatorLoc();284  else if (const auto *Op =285               Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))286    Loc = Op->getOperatorLoc();287 288  if (Loc.isInvalid())289    return;290 291  Loc = Result.SourceManager->getSpellingLoc(Loc);292  if (Loc.isInvalid() || Loc.isMacroID())293    return;294 295  const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);296  if (TokenRange.isInvalid())297    return;298 299  const StringRef Spelling = Lexer::getSourceText(300      TokenRange, *Result.SourceManager, Result.Context->getLangOpts());301  const StringRef TranslatedSpelling = translate(Spelling);302 303  if (TranslatedSpelling.empty())304    return;305 306  std::string FixSpelling = TranslatedSpelling.str();307 308  StringRef SourceRepresentation = "an alternative";309  StringRef TargetRepresentation = "a traditional";310  if (needEscaping(TranslatedSpelling)) {311    SourceRepresentation = "a traditional";312    TargetRepresentation = "an alternative";313 314    const StringRef SpellingEx = Lexer::getSourceText(315        CharSourceRange::getCharRange(316            TokenRange.getBegin().getLocWithOffset(-1),317            TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),318        *Result.SourceManager, Result.Context->getLangOpts());319    if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))320      FixSpelling.insert(FixSpelling.begin(), ' ');321    if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))322      FixSpelling.push_back(' ');323  }324 325  diag(326      Loc,327      "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")328      << Spelling << SourceRepresentation << TargetRepresentation329      << TranslatedSpelling330      << FixItHint::CreateReplacement(TokenRange, FixSpelling);331}332 333} // namespace clang::tidy::readability334