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