brintos

brintos / llvm-project-archived public Read only

0
0
Text · 18.2 KiB · 098d46c Raw
552 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 "MacroToEnumCheck.h"10#include "IntegralLiteralExpressionMatcher.h"11 12#include "clang/AST/ASTContext.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/Lex/Preprocessor.h"15#include "llvm/ADT/STLExtras.h"16#include <cassert>17#include <cctype>18#include <string>19 20namespace clang::tidy::modernize {21 22static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options,23                            StringRef Text) {24  // Use a lexer to look for tokens; if we find something other than a single25  // hash, then there were intervening tokens between macro definitions.26  const std::string Buffer{Text};27  Lexer Lex(Loc, Options, Buffer.c_str(), Buffer.c_str(),28            Buffer.c_str() + Buffer.size());29  Token Tok;30  bool SeenHash = false;31  while (!Lex.LexFromRawLexer(Tok)) {32    if (Tok.getKind() == tok::hash && !SeenHash) {33      SeenHash = true;34      continue;35    }36    return false;37  }38 39  // Everything in between was whitespace, so now just look for two blank lines,40  // consisting of two consecutive EOL sequences, either '\n', '\r' or '\r\n'.41  enum class WhiteSpace {42    Nothing,43    CR,44    LF,45    CRLF,46    CRLFCR,47  };48 49  WhiteSpace State = WhiteSpace::Nothing;50  for (const char C : Text) {51    switch (C) {52    case '\r':53      if (State == WhiteSpace::CR)54        return false;55 56      State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;57      break;58 59    case '\n':60      if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)61        return false;62 63      State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;64      break;65 66    default:67      State = WhiteSpace::Nothing;68      break;69    }70  }71 72  return true;73}74 75static StringRef getTokenName(const Token &Tok) {76  return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()77                                     : Tok.getIdentifierInfo()->getName();78}79 80namespace {81 82struct EnumMacro {83  EnumMacro(Token Name, const MacroDirective *Directive)84      : Name(Name), Directive(Directive) {}85 86  Token Name;87  const MacroDirective *Directive;88};89 90using MacroList = SmallVector<EnumMacro>;91 92enum class IncludeGuard { None, FileChanged, IfGuard, DefineGuard };93 94struct FileState {95  FileState() = default;96 97  int ConditionScopes = 0;98  unsigned int LastLine = 0;99  IncludeGuard GuardScanner = IncludeGuard::None;100  SourceLocation LastMacroLocation;101};102 103} // namespace104 105class MacroToEnumCallbacks : public PPCallbacks {106public:107  MacroToEnumCallbacks(MacroToEnumCheck *Check, const LangOptions &LangOptions,108                       const SourceManager &SM)109      : Check(Check), LangOpts(LangOptions), SM(SM) {}110 111  void FileChanged(SourceLocation Loc, FileChangeReason Reason,112                   SrcMgr::CharacteristicKind FileType,113                   FileID PrevFID) override;114 115  void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,116                          StringRef FileName, bool IsAngled,117                          CharSourceRange FilenameRange,118                          OptionalFileEntryRef File, StringRef SearchPath,119                          StringRef RelativePath, const Module *SuggestedModule,120                          bool ModuleImported,121                          SrcMgr::CharacteristicKind FileType) override {122    clearCurrentEnum(HashLoc);123  }124 125  // Keep track of macro definitions that look like enums.126  void MacroDefined(const Token &MacroNameTok,127                    const MacroDirective *MD) override;128 129  // Undefining an enum-like macro results in the enum set being dropped.130  void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,131                      const MacroDirective *Undef) override;132 133  // Conditional compilation clears any adjacent enum-like macros.134  // Macros used in conditional expressions clear any adjacent enum-like135  // macros.136  // Include guards are either137  //   #if !defined(GUARD)138  // or139  //   #ifndef GUARD140  void If(SourceLocation Loc, SourceRange ConditionRange,141          ConditionValueKind ConditionValue) override {142    conditionStart(Loc);143    checkCondition(ConditionRange);144  }145  void Ifndef(SourceLocation Loc, const Token &MacroNameTok,146              const MacroDefinition &MD) override {147    conditionStart(Loc);148    checkName(MacroNameTok);149  }150  void Ifdef(SourceLocation Loc, const Token &MacroNameTok,151             const MacroDefinition &MD) override {152    conditionStart(Loc);153    checkName(MacroNameTok);154  }155  void Elif(SourceLocation Loc, SourceRange ConditionRange,156            ConditionValueKind ConditionValue, SourceLocation IfLoc) override {157    checkCondition(ConditionRange);158  }159  void Elifdef(SourceLocation Loc, const Token &MacroNameTok,160               const MacroDefinition &MD) override {161    checkName(MacroNameTok);162  }163  void Elifdef(SourceLocation Loc, SourceRange ConditionRange,164               SourceLocation IfLoc) override {165    PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc);166  }167  void Elifndef(SourceLocation Loc, const Token &MacroNameTok,168                const MacroDefinition &MD) override {169    checkName(MacroNameTok);170  }171  void Elifndef(SourceLocation Loc, SourceRange ConditionRange,172                SourceLocation IfLoc) override {173    PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc);174  }175  void Endif(SourceLocation Loc, SourceLocation IfLoc) override;176  void PragmaDirective(SourceLocation Loc,177                       PragmaIntroducerKind Introducer) override;178 179  // After we've seen everything, issue warnings and fix-its.180  void EndOfMainFile() override;181 182  void invalidateRange(SourceRange Range);183 184private:185  void newEnum() {186    if (Enums.empty() || !Enums.back().empty())187      Enums.emplace_back();188  }189  bool insideConditional() const {190    return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&191            CurrentFile->ConditionScopes > 1) ||192           (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&193            CurrentFile->ConditionScopes > 0);194  }195  bool isConsecutiveMacro(const MacroDirective *MD) const;196  void rememberLastMacroLocation(const MacroDirective *MD) {197    CurrentFile->LastLine = SM.getSpellingLineNumber(MD->getLocation());198    CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(199        MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);200  }201  void clearLastMacroLocation() {202    CurrentFile->LastLine = 0;203    CurrentFile->LastMacroLocation = SourceLocation{};204  }205  void clearCurrentEnum(SourceLocation Loc);206  void conditionStart(const SourceLocation &Loc);207  void checkCondition(SourceRange ConditionRange);208  void checkName(const Token &MacroNameTok);209  void rememberExpressionName(const Token &Tok);210  void rememberExpressionTokens(ArrayRef<Token> MacroTokens);211  void invalidateExpressionNames();212  void issueDiagnostics();213  void warnMacroEnum(const EnumMacro &Macro) const;214  void fixEnumMacro(const MacroList &MacroList) const;215  bool isInitializer(ArrayRef<Token> MacroTokens);216 217  MacroToEnumCheck *Check;218  const LangOptions &LangOpts;219  const SourceManager &SM;220  SmallVector<MacroList> Enums;221  SmallVector<FileState> Files;222  std::vector<std::string> ExpressionNames;223  FileState *CurrentFile = nullptr;224};225 226bool MacroToEnumCallbacks::isConsecutiveMacro(const MacroDirective *MD) const {227  if (CurrentFile->LastMacroLocation.isInvalid())228    return false;229 230  const SourceLocation Loc = MD->getLocation();231  if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc))232    return true;233 234  const SourceLocation Define =235      SM.translateLineCol(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc), 1);236  const CharSourceRange BetweenMacros{237      SourceRange{CurrentFile->LastMacroLocation, Define}, true};238  const CharSourceRange CharRange =239      Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);240  const StringRef BetweenText = Lexer::getSourceText(CharRange, SM, LangOpts);241  return hasOnlyComments(Define, LangOpts, BetweenText);242}243 244void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation Loc) {245  // Only drop the most recent Enum set if the directive immediately follows.246  if (!Enums.empty() && !Enums.back().empty() &&247      SM.getSpellingLineNumber(Loc) == CurrentFile->LastLine + 1)248    Enums.pop_back();249 250  clearLastMacroLocation();251}252 253void MacroToEnumCallbacks::conditionStart(const SourceLocation &Loc) {254  ++CurrentFile->ConditionScopes;255  clearCurrentEnum(Loc);256  if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)257    CurrentFile->GuardScanner = IncludeGuard::IfGuard;258}259 260void MacroToEnumCallbacks::checkCondition(SourceRange Range) {261  const CharSourceRange CharRange = Lexer::makeFileCharRange(262      CharSourceRange::getTokenRange(Range), SM, LangOpts);263  std::string Text = Lexer::getSourceText(CharRange, SM, LangOpts).str();264  Lexer Lex(CharRange.getBegin(), LangOpts, Text.data(), Text.data(),265            Text.data() + Text.size());266  Token Tok;267  bool End = false;268  while (!End) {269    End = Lex.LexFromRawLexer(Tok);270    if (Tok.is(tok::raw_identifier) &&271        Tok.getRawIdentifier().str() != "defined")272      checkName(Tok);273  }274}275 276void MacroToEnumCallbacks::checkName(const Token &MacroNameTok) {277  rememberExpressionName(MacroNameTok);278 279  StringRef Id = getTokenName(MacroNameTok);280  llvm::erase_if(Enums, [&Id](const MacroList &MacroList) {281    return llvm::any_of(MacroList, [&Id](const EnumMacro &Macro) {282      return getTokenName(Macro.Name) == Id;283    });284  });285}286 287void MacroToEnumCallbacks::rememberExpressionName(const Token &Tok) {288  const std::string Id = getTokenName(Tok).str();289  auto Pos = llvm::lower_bound(ExpressionNames, Id);290  if (Pos == ExpressionNames.end() || *Pos != Id) {291    ExpressionNames.insert(Pos, Id);292  }293}294 295void MacroToEnumCallbacks::rememberExpressionTokens(296    ArrayRef<Token> MacroTokens) {297  for (const Token Tok : MacroTokens) {298    if (Tok.isAnyIdentifier())299      rememberExpressionName(Tok);300  }301}302 303void MacroToEnumCallbacks::FileChanged(SourceLocation Loc,304                                       FileChangeReason Reason,305                                       SrcMgr::CharacteristicKind FileType,306                                       FileID PrevFID) {307  newEnum();308  if (Reason == EnterFile) {309    Files.emplace_back();310    if (!SM.isInMainFile(Loc))311      Files.back().GuardScanner = IncludeGuard::FileChanged;312  } else if (Reason == ExitFile) {313    assert(CurrentFile->ConditionScopes == 0);314    Files.pop_back();315  }316  CurrentFile = &Files.back();317}318 319bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens) {320  IntegralLiteralExpressionMatcher Matcher(MacroTokens, LangOpts.C99 == 0);321  const bool Matched = Matcher.match();322  const bool IsC = !LangOpts.CPlusPlus;323  if (IsC && (Matcher.largestLiteralSize() != LiteralSize::Int &&324              Matcher.largestLiteralSize() != LiteralSize::UnsignedInt))325    return false;326 327  return Matched;328}329 330// Any defined but rejected macro is scanned for identifiers that331// are to be excluded as enums.332void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,333                                        const MacroDirective *MD) {334  // Include guards are never candidates for becoming an enum.335  if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {336    CurrentFile->GuardScanner = IncludeGuard::DefineGuard;337    return;338  }339 340  if (insideConditional())341    return;342 343  if (SM.getFilename(MD->getLocation()).empty())344    return;345 346  const MacroInfo *Info = MD->getMacroInfo();347  const ArrayRef<Token> MacroTokens = Info->tokens();348  if (Info->isBuiltinMacro() || MacroTokens.empty())349    return;350  if (Info->isFunctionLike()) {351    rememberExpressionTokens(MacroTokens);352    return;353  }354 355  if (!isInitializer(MacroTokens))356    return;357 358  if (!isConsecutiveMacro(MD))359    newEnum();360  Enums.back().emplace_back(MacroNameTok, MD);361  rememberLastMacroLocation(MD);362}363 364// Any macro that is undefined removes all adjacent macros from consideration as365// an enum and starts a new enum scan.366void MacroToEnumCallbacks::MacroUndefined(const Token &MacroNameTok,367                                          const MacroDefinition &MD,368                                          const MacroDirective *Undef) {369  rememberExpressionName(MacroNameTok);370 371  auto MatchesToken = [&MacroNameTok](const EnumMacro &Macro) {372    return getTokenName(Macro.Name) == getTokenName(MacroNameTok);373  };374 375  auto *It = llvm::find_if(Enums, [MatchesToken](const MacroList &MacroList) {376    return llvm::any_of(MacroList, MatchesToken);377  });378  if (It != Enums.end())379    Enums.erase(It);380 381  clearLastMacroLocation();382  CurrentFile->GuardScanner = IncludeGuard::None;383}384 385void MacroToEnumCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) {386  // The if directive for the include guard isn't counted in the387  // ConditionScopes.388  if (CurrentFile->ConditionScopes == 0 &&389      CurrentFile->GuardScanner == IncludeGuard::DefineGuard)390    return;391 392  // We don't need to clear the current enum because the start of the393  // conditional block already took care of that.394  assert(CurrentFile->ConditionScopes > 0);395  --CurrentFile->ConditionScopes;396}397 398template <size_t N>399static bool textEquals(const char (&Needle)[N], const char *HayStack) {400  return StringRef{HayStack, N - 1} == Needle;401}402 403template <size_t N> static size_t len(const char (&)[N]) { return N - 1; }404 405void MacroToEnumCallbacks::PragmaDirective(SourceLocation Loc,406                                           PragmaIntroducerKind Introducer) {407  if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)408    return;409 410  bool Invalid = false;411  const char *Text = SM.getCharacterData(412      Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);413  if (Invalid)414    return;415 416  while (*Text && std::isspace(*Text))417    ++Text;418 419  if (textEquals("pragma", Text))420    return;421 422  Text += len("pragma");423  while (*Text && std::isspace(*Text))424    ++Text;425 426  if (textEquals("once", Text))427    CurrentFile->GuardScanner = IncludeGuard::IfGuard;428}429 430void MacroToEnumCallbacks::invalidateExpressionNames() {431  for (const std::string &Id : ExpressionNames) {432    llvm::erase_if(Enums, [Id](const MacroList &MacroList) {433      return llvm::any_of(MacroList, [&Id](const EnumMacro &Macro) {434        return getTokenName(Macro.Name) == Id;435      });436    });437  }438}439 440void MacroToEnumCallbacks::EndOfMainFile() {441  invalidateExpressionNames();442  issueDiagnostics();443}444 445void MacroToEnumCallbacks::invalidateRange(SourceRange Range) {446  llvm::erase_if(Enums, [Range](const MacroList &MacroList) {447    return llvm::any_of(MacroList, [Range](const EnumMacro &Macro) {448      return Macro.Directive->getLocation() >= Range.getBegin() &&449             Macro.Directive->getLocation() <= Range.getEnd();450    });451  });452}453 454void MacroToEnumCallbacks::issueDiagnostics() {455  for (const MacroList &MacroList : Enums) {456    if (MacroList.empty())457      continue;458 459    for (const EnumMacro &Macro : MacroList)460      warnMacroEnum(Macro);461 462    fixEnumMacro(MacroList);463  }464}465 466void MacroToEnumCallbacks::warnMacroEnum(const EnumMacro &Macro) const {467  Check->diag(Macro.Directive->getLocation(),468              "macro '%0' defines an integral constant; prefer an enum instead")469      << getTokenName(Macro.Name);470}471 472void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const {473  SourceLocation Begin =474      MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();475  Begin = SM.translateLineCol(SM.getFileID(Begin),476                              SM.getSpellingLineNumber(Begin), 1);477  const DiagnosticBuilder Diagnostic =478      Check->diag(Begin, "replace macro with enum")479      << FixItHint::CreateInsertion(Begin, "enum {\n");480 481  for (size_t I = 0U; I < MacroList.size(); ++I) {482    const EnumMacro &Macro = MacroList[I];483    const SourceLocation DefineEnd =484        Macro.Directive->getMacroInfo()->getDefinitionLoc();485    const SourceLocation DefineBegin = SM.translateLineCol(486        SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);487    CharSourceRange DefineRange;488    DefineRange.setBegin(DefineBegin);489    DefineRange.setEnd(DefineEnd);490    Diagnostic << FixItHint::CreateRemoval(DefineRange);491 492    const SourceLocation NameEnd = Lexer::getLocForEndOfToken(493        Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);494    Diagnostic << FixItHint::CreateInsertion(NameEnd, " =");495 496    const SourceLocation ValueEnd = Lexer::getLocForEndOfToken(497        Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,498        LangOpts);499    if (I < MacroList.size() - 1)500      Diagnostic << FixItHint::CreateInsertion(ValueEnd, ",");501  }502 503  SourceLocation End = Lexer::getLocForEndOfToken(504      MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,505      LangOpts);506  End = SM.translateLineCol(SM.getFileID(End),507                            SM.getSpellingLineNumber(End) + 1, 1);508  Diagnostic << FixItHint::CreateInsertion(End, "};\n");509}510 511void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM,512                                           Preprocessor *PP,513                                           Preprocessor *ModuleExpanderPP) {514  auto Callback =515      std::make_unique<MacroToEnumCallbacks>(this, getLangOpts(), SM);516  PPCallback = Callback.get();517  PP->addPPCallbacks(std::move(Callback));518}519 520void MacroToEnumCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {521  using namespace ast_matchers;522  auto TopLevelDecl = hasParent(translationUnitDecl());523  Finder->addMatcher(decl(TopLevelDecl).bind("top"), this);524}525 526static bool isValid(SourceRange Range) {527  return Range.getBegin().isValid() && Range.getEnd().isValid();528}529 530static bool empty(SourceRange Range) {531  return Range.getBegin() == Range.getEnd();532}533 534void MacroToEnumCheck::check(535    const ast_matchers::MatchFinder::MatchResult &Result) {536  auto *TLDecl = Result.Nodes.getNodeAs<Decl>("top");537  if (TLDecl == nullptr)538    return;539 540  SourceRange Range = TLDecl->getSourceRange();541  if (auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>("top")) {542    if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody())543      Range = SourceRange{TemplateFn->getBeginLoc(),544                          TemplateFn->getUnderlyingDecl()->getBodyRBrace()};545  }546 547  if (isValid(Range) && !empty(Range))548    PPCallback->invalidateRange(Range);549}550 551} // namespace clang::tidy::modernize552