brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.5 KiB · e2e7bba Raw
246 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 "UseOverrideCheck.h"10#include "../utils/LexerUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Lex/Lexer.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::modernize {18 19UseOverrideCheck::UseOverrideCheck(StringRef Name, ClangTidyContext *Context)20    : ClangTidyCheck(Name, Context),21      IgnoreDestructors(Options.get("IgnoreDestructors", false)),22      IgnoreTemplateInstantiations(23          Options.get("IgnoreTemplateInstantiations", false)),24      AllowOverrideAndFinal(Options.get("AllowOverrideAndFinal", false)),25      OverrideSpelling(Options.get("OverrideSpelling", "override")),26      FinalSpelling(Options.get("FinalSpelling", "final")) {}27 28void UseOverrideCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {29  Options.store(Opts, "IgnoreDestructors", IgnoreDestructors);30  Options.store(Opts, "IgnoreTemplateInstantiations",31                IgnoreTemplateInstantiations);32  Options.store(Opts, "AllowOverrideAndFinal", AllowOverrideAndFinal);33  Options.store(Opts, "OverrideSpelling", OverrideSpelling);34  Options.store(Opts, "FinalSpelling", FinalSpelling);35}36 37void UseOverrideCheck::registerMatchers(MatchFinder *Finder) {38  auto IgnoreDestructorMatcher =39      IgnoreDestructors ? cxxMethodDecl(unless(cxxDestructorDecl()))40                        : cxxMethodDecl();41  auto IgnoreTemplateInstantiationsMatcher =42      IgnoreTemplateInstantiations43          ? cxxMethodDecl(unless(ast_matchers::isTemplateInstantiation()))44          : cxxMethodDecl();45  Finder->addMatcher(cxxMethodDecl(isOverride(),46                                   IgnoreTemplateInstantiationsMatcher,47                                   IgnoreDestructorMatcher)48                         .bind("method"),49                     this);50}51 52// Re-lex the tokens to get precise locations to insert 'override' and remove53// 'virtual'.54static SmallVector<Token, 16>55parseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) {56  const SourceManager &Sources = *Result.SourceManager;57  const std::pair<FileID, unsigned> LocInfo =58      Sources.getDecomposedLoc(Range.getBegin());59  const StringRef File = Sources.getBufferData(LocInfo.first);60  const char *TokenBegin = File.data() + LocInfo.second;61  Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first),62                 Result.Context->getLangOpts(), File.begin(), TokenBegin,63                 File.end());64  SmallVector<Token, 16> Tokens;65  Token Tok;66  int NestedParens = 0;67  while (!RawLexer.LexFromRawLexer(Tok)) {68    if ((Tok.is(tok::semi) || Tok.is(tok::l_brace)) && NestedParens == 0)69      break;70    if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation()))71      break;72    if (Tok.is(tok::l_paren))73      ++NestedParens;74    else if (Tok.is(tok::r_paren))75      --NestedParens;76    if (Tok.is(tok::raw_identifier)) {77      IdentifierInfo &Info = Result.Context->Idents.get(StringRef(78          Sources.getCharacterData(Tok.getLocation()), Tok.getLength()));79      Tok.setIdentifierInfo(&Info);80      Tok.setKind(Info.getTokenID());81    }82    Tokens.push_back(Tok);83  }84  return Tokens;85}86 87static StringRef getText(const Token &Tok, const SourceManager &Sources) {88  return {Sources.getCharacterData(Tok.getLocation()), Tok.getLength()};89}90 91void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) {92  const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>("method");93  const SourceManager &Sources = *Result.SourceManager;94 95  ASTContext &Context = *Result.Context;96 97  assert(Method != nullptr);98  if (Method->getInstantiatedFromMemberFunction() != nullptr)99    Method = Method->getInstantiatedFromMemberFunction();100 101  if (Method->isImplicit() || Method->getLocation().isMacroID() ||102      Method->isOutOfLine())103    return;104 105  const bool HasVirtual = Method->isVirtualAsWritten();106  const bool HasOverride = Method->getAttr<OverrideAttr>();107  const bool HasFinal = Method->getAttr<FinalAttr>();108 109  const bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal;110  const unsigned KeywordCount = HasVirtual + HasOverride + HasFinal;111 112  if ((!OnlyVirtualSpecified && KeywordCount == 1) ||113      (!HasVirtual && HasOverride && HasFinal && AllowOverrideAndFinal))114    return; // Nothing to do.115 116  std::string Message;117  if (OnlyVirtualSpecified) {118    Message = "prefer using '%0' or (rarely) '%1' instead of 'virtual'";119  } else if (KeywordCount == 0) {120    Message = "annotate this function with '%0' or (rarely) '%1'";121  } else {122    const StringRef Redundant =123        HasVirtual ? (HasOverride && HasFinal && !AllowOverrideAndFinal124                          ? "'virtual' and '%0' are"125                          : "'virtual' is")126                   : "'%0' is";127    const StringRef Correct = HasFinal ? "'%1'" : "'%0'";128 129    Message = (llvm::Twine(Redundant) +130               " redundant since the function is already declared " + Correct)131                  .str();132  }133 134  auto Diag = diag(Method->getLocation(), Message)135              << OverrideSpelling << FinalSpelling;136 137  const CharSourceRange FileRange = Lexer::makeFileCharRange(138      CharSourceRange::getTokenRange(Method->getSourceRange()), Sources,139      getLangOpts());140 141  if (!FileRange.isValid())142    return;143 144  // FIXME: Instead of re-lexing and looking for specific macros such as145  // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each146  // FunctionDecl.147  SmallVector<Token, 16> Tokens = parseTokens(FileRange, Result);148 149  // Add 'override' on inline declarations that don't already have it.150  if (!HasFinal && !HasOverride) {151    SourceLocation InsertLoc;152    std::string ReplacementText = (OverrideSpelling + " ").str();153    const SourceLocation MethodLoc = Method->getLocation();154 155    for (const Token T : Tokens) {156      if (T.is(tok::kw___attribute) &&157          !Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) {158        InsertLoc = T.getLocation();159        break;160      }161    }162 163    if (Method->hasAttrs()) {164      for (const clang::Attr *A : Method->getAttrs()) {165        if (!A->isImplicit() && !A->isInherited()) {166          const SourceLocation Loc =167              Sources.getExpansionLoc(A->getRange().getBegin());168          if ((!InsertLoc.isValid() ||169               Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) &&170              !Sources.isBeforeInTranslationUnit(Loc, MethodLoc))171            InsertLoc = Loc;172        }173      }174    }175 176    if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() &&177        Method->getBody() && !Method->isDefaulted()) {178      // For methods with inline definition, add the override keyword at the179      // end of the declaration of the function, but prefer to put it on the180      // same line as the declaration if the beginning brace for the start of181      // the body falls on the next line.182      ReplacementText = (" " + OverrideSpelling).str();183      auto *LastTokenIter = std::prev(Tokens.end());184      // When try statement is used instead of compound statement as185      // method body - insert override keyword before it.186      if (LastTokenIter->is(tok::kw_try))187        LastTokenIter = std::prev(LastTokenIter);188      InsertLoc = LastTokenIter->getEndLoc();189    }190 191    if (!InsertLoc.isValid()) {192      // For declarations marked with "= 0" or "= [default|delete]", the end193      // location will point until after those markings. Therefore, the override194      // keyword shouldn't be inserted at the end, but before the '='.195      if (Tokens.size() > 2 &&196          (getText(Tokens.back(), Sources) == "0" ||197           Tokens.back().is(tok::kw_default) ||198           Tokens.back().is(tok::kw_delete)) &&199          getText(Tokens[Tokens.size() - 2], Sources) == "=") {200        InsertLoc = Tokens[Tokens.size() - 2].getLocation();201        // Check if we need to insert a space.202        if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0)203          ReplacementText = (" " + OverrideSpelling + " ").str();204      } else if (getText(Tokens.back(), Sources) == "ABSTRACT")205        InsertLoc = Tokens.back().getLocation();206    }207 208    if (!InsertLoc.isValid()) {209      InsertLoc = FileRange.getEnd();210      ReplacementText = (" " + OverrideSpelling).str();211    }212 213    // If the override macro has been specified just ensure it exists,214    // if not don't apply a fixit but keep the warning.215    if (OverrideSpelling != "override" &&216        !Context.Idents.get(OverrideSpelling).hasMacroDefinition())217      return;218 219    Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText);220  }221 222  if (HasFinal && HasOverride && !AllowOverrideAndFinal) {223    const SourceLocation OverrideLoc =224        Method->getAttr<OverrideAttr>()->getLocation();225    Diag << FixItHint::CreateRemoval(226        CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc));227  }228 229  if (HasVirtual) {230    for (const Token Tok : Tokens) {231      if (Tok.is(tok::kw_virtual)) {232        std::optional<Token> NextToken =233            utils::lexer::findNextTokenIncludingComments(234                Tok.getEndLoc(), Sources, getLangOpts());235        if (NextToken.has_value()) {236          Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(237              Tok.getLocation(), NextToken->getLocation()));238          break;239        }240      }241    }242  }243}244 245} // namespace clang::tidy::modernize246