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