//===--- 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 ParseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) { const SourceManager &Sources = *Result.SourceManager; std::pair 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 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("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(); bool HasFinal = Method->getAttr(); 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 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()->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