234 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===--- UseOverrideCheck.cpp - clang-tidy --------------------------------===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "UseOverrideCheck.h"
 | |
| #include "clang/AST/ASTContext.h"
 | |
| #include "clang/ASTMatchers/ASTMatchFinder.h"
 | |
| #include "clang/Lex/Lexer.h"
 | |
| 
 | |
| using namespace clang::ast_matchers;
 | |
| 
 | |
| namespace clang {
 | |
| namespace tidy {
 | |
| namespace modernize {
 | |
| 
 | |
| UseOverrideCheck::UseOverrideCheck(StringRef Name, ClangTidyContext *Context)
 | |
|     : ClangTidyCheck(Name, Context),
 | |
|       IgnoreDestructors(Options.get("IgnoreDestructors", false)),
 | |
|       AllowOverrideAndFinal(Options.get("AllowOverrideAndFinal", false)),
 | |
|       OverrideSpelling(Options.get("OverrideSpelling", "override")),
 | |
|       FinalSpelling(Options.get("FinalSpelling", "final")) {}
 | |
| 
 | |
| void UseOverrideCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
 | |
|   Options.store(Opts, "IgnoreDestructors", IgnoreDestructors);
 | |
|   Options.store(Opts, "AllowOverrideAndFinal", AllowOverrideAndFinal);
 | |
|   Options.store(Opts, "OverrideSpelling", OverrideSpelling);
 | |
|   Options.store(Opts, "FinalSpelling", FinalSpelling);
 | |
| }
 | |
| 
 | |
| void UseOverrideCheck::registerMatchers(MatchFinder *Finder) {
 | |
|   if (IgnoreDestructors)
 | |
|     Finder->addMatcher(
 | |
|         cxxMethodDecl(isOverride(), unless(cxxDestructorDecl())).bind("method"),
 | |
|         this);
 | |
|   else
 | |
|     Finder->addMatcher(cxxMethodDecl(isOverride()).bind("method"), this);
 | |
| }
 | |
| 
 | |
| // Re-lex the tokens to get precise locations to insert 'override' and remove
 | |
| // 'virtual'.
 | |
| static SmallVector<Token, 16>
 | |
| parseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) {
 | |
|   const SourceManager &Sources = *Result.SourceManager;
 | |
|   std::pair<FileID, unsigned> LocInfo =
 | |
|       Sources.getDecomposedLoc(Range.getBegin());
 | |
|   StringRef File = Sources.getBufferData(LocInfo.first);
 | |
|   const char *TokenBegin = File.data() + LocInfo.second;
 | |
|   Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first),
 | |
|                  Result.Context->getLangOpts(), File.begin(), TokenBegin,
 | |
|                  File.end());
 | |
|   SmallVector<Token, 16> Tokens;
 | |
|   Token Tok;
 | |
|   int NestedParens = 0;
 | |
|   while (!RawLexer.LexFromRawLexer(Tok)) {
 | |
|     if ((Tok.is(tok::semi) || Tok.is(tok::l_brace)) && NestedParens == 0)
 | |
|       break;
 | |
|     if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation()))
 | |
|       break;
 | |
|     if (Tok.is(tok::l_paren))
 | |
|       ++NestedParens;
 | |
|     else if (Tok.is(tok::r_paren))
 | |
|       --NestedParens;
 | |
|     if (Tok.is(tok::raw_identifier)) {
 | |
|       IdentifierInfo &Info = Result.Context->Idents.get(StringRef(
 | |
|           Sources.getCharacterData(Tok.getLocation()), Tok.getLength()));
 | |
|       Tok.setIdentifierInfo(&Info);
 | |
|       Tok.setKind(Info.getTokenID());
 | |
|     }
 | |
|     Tokens.push_back(Tok);
 | |
|   }
 | |
|   return Tokens;
 | |
| }
 | |
| 
 | |
| static StringRef getText(const Token &Tok, const SourceManager &Sources) {
 | |
|   return StringRef(Sources.getCharacterData(Tok.getLocation()),
 | |
|                    Tok.getLength());
 | |
| }
 | |
| 
 | |
| void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) {
 | |
|   const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>("method");
 | |
|   const SourceManager &Sources = *Result.SourceManager;
 | |
| 
 | |
|   ASTContext &Context = *Result.Context;
 | |
| 
 | |
|   assert(Method != nullptr);
 | |
|   if (Method->getInstantiatedFromMemberFunction() != nullptr)
 | |
|     Method = Method->getInstantiatedFromMemberFunction();
 | |
| 
 | |
|   if (Method->isImplicit() || Method->getLocation().isMacroID() ||
 | |
|       Method->isOutOfLine())
 | |
|     return;
 | |
| 
 | |
|   bool HasVirtual = Method->isVirtualAsWritten();
 | |
|   bool HasOverride = Method->getAttr<OverrideAttr>();
 | |
|   bool HasFinal = Method->getAttr<FinalAttr>();
 | |
| 
 | |
|   bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal;
 | |
|   unsigned KeywordCount = HasVirtual + HasOverride + HasFinal;
 | |
| 
 | |
|   if ((!OnlyVirtualSpecified && KeywordCount == 1) ||
 | |
|       (!HasVirtual && HasOverride && HasFinal && AllowOverrideAndFinal))
 | |
|     return; // Nothing to do.
 | |
| 
 | |
|   std::string Message;
 | |
|   if (OnlyVirtualSpecified) {
 | |
|     Message = "prefer using '%0' or (rarely) '%1' instead of 'virtual'";
 | |
|   } else if (KeywordCount == 0) {
 | |
|     Message = "annotate this function with '%0' or (rarely) '%1'";
 | |
|   } else {
 | |
|     StringRef Redundant =
 | |
|         HasVirtual ? (HasOverride && HasFinal && !AllowOverrideAndFinal
 | |
|                           ? "'virtual' and '%0' are"
 | |
|                           : "'virtual' is")
 | |
|                    : "'%0' is";
 | |
|     StringRef Correct = HasFinal ? "'%1'" : "'%0'";
 | |
| 
 | |
|     Message = (llvm::Twine(Redundant) +
 | |
|                " redundant since the function is already declared " + Correct)
 | |
|                   .str();
 | |
|   }
 | |
| 
 | |
|   auto Diag = diag(Method->getLocation(), Message)
 | |
|               << OverrideSpelling << FinalSpelling;
 | |
| 
 | |
|   CharSourceRange FileRange = Lexer::makeFileCharRange(
 | |
|       CharSourceRange::getTokenRange(Method->getSourceRange()), Sources,
 | |
|       getLangOpts());
 | |
| 
 | |
|   if (!FileRange.isValid())
 | |
|     return;
 | |
| 
 | |
|   // FIXME: Instead of re-lexing and looking for specific macros such as
 | |
|   // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each
 | |
|   // FunctionDecl.
 | |
|   SmallVector<Token, 16> Tokens = parseTokens(FileRange, Result);
 | |
| 
 | |
|   // Add 'override' on inline declarations that don't already have it.
 | |
|   if (!HasFinal && !HasOverride) {
 | |
|     SourceLocation InsertLoc;
 | |
|     std::string ReplacementText = OverrideSpelling + " ";
 | |
|     SourceLocation MethodLoc = Method->getLocation();
 | |
| 
 | |
|     for (Token T : Tokens) {
 | |
|       if (T.is(tok::kw___attribute) &&
 | |
|           !Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) {
 | |
|         InsertLoc = T.getLocation();
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (Method->hasAttrs()) {
 | |
|       for (const clang::Attr *A : Method->getAttrs()) {
 | |
|         if (!A->isImplicit() && !A->isInherited()) {
 | |
|           SourceLocation Loc =
 | |
|               Sources.getExpansionLoc(A->getRange().getBegin());
 | |
|           if ((!InsertLoc.isValid() ||
 | |
|                Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) &&
 | |
|               !Sources.isBeforeInTranslationUnit(Loc, MethodLoc))
 | |
|             InsertLoc = Loc;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() &&
 | |
|         Method->getBody() && !Method->isDefaulted()) {
 | |
|       // For methods with inline definition, add the override keyword at the
 | |
|       // end of the declaration of the function, but prefer to put it on the
 | |
|       // same line as the declaration if the beginning brace for the start of
 | |
|       // the body falls on the next line.
 | |
|       ReplacementText = " " + OverrideSpelling;
 | |
|       auto *LastTokenIter = std::prev(Tokens.end());
 | |
|       // When try statement is used instead of compound statement as
 | |
|       // method body - insert override keyword before it.
 | |
|       if (LastTokenIter->is(tok::kw_try))
 | |
|         LastTokenIter = std::prev(LastTokenIter);
 | |
|       InsertLoc = LastTokenIter->getEndLoc();
 | |
|     }
 | |
| 
 | |
|     if (!InsertLoc.isValid()) {
 | |
|       // For declarations marked with "= 0" or "= [default|delete]", the end
 | |
|       // location will point until after those markings. Therefore, the override
 | |
|       // keyword shouldn't be inserted at the end, but before the '='.
 | |
|       if (Tokens.size() > 2 &&
 | |
|           (getText(Tokens.back(), Sources) == "0" ||
 | |
|            Tokens.back().is(tok::kw_default) ||
 | |
|            Tokens.back().is(tok::kw_delete)) &&
 | |
|           getText(Tokens[Tokens.size() - 2], Sources) == "=") {
 | |
|         InsertLoc = Tokens[Tokens.size() - 2].getLocation();
 | |
|         // Check if we need to insert a space.
 | |
|         if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0)
 | |
|           ReplacementText = " " + OverrideSpelling + " ";
 | |
|       } else if (getText(Tokens.back(), Sources) == "ABSTRACT")
 | |
|         InsertLoc = Tokens.back().getLocation();
 | |
|     }
 | |
| 
 | |
|     if (!InsertLoc.isValid()) {
 | |
|       InsertLoc = FileRange.getEnd();
 | |
|       ReplacementText = " " + OverrideSpelling;
 | |
|     }
 | |
| 
 | |
|     // If the override macro has been specified just ensure it exists,
 | |
|     // if not don't apply a fixit but keep the warning.
 | |
|     if (OverrideSpelling != "override" &&
 | |
|         !Context.Idents.get(OverrideSpelling).hasMacroDefinition())
 | |
|       return;
 | |
| 
 | |
|     Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText);
 | |
|   }
 | |
| 
 | |
|   if (HasFinal && HasOverride && !AllowOverrideAndFinal) {
 | |
|     SourceLocation OverrideLoc = Method->getAttr<OverrideAttr>()->getLocation();
 | |
|     Diag << FixItHint::CreateRemoval(
 | |
|         CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc));
 | |
|   }
 | |
| 
 | |
|   if (HasVirtual) {
 | |
|     for (Token Tok : Tokens) {
 | |
|       if (Tok.is(tok::kw_virtual)) {
 | |
|         Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
 | |
|             Tok.getLocation(), Tok.getLocation()));
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| } // namespace modernize
 | |
| } // namespace tidy
 | |
| } // namespace clang
 |