clang-tools  15.0.0git
UseOverrideCheck.cpp
Go to the documentation of this file.
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 
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 
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 
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>
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 
78 static StringRef getText(const Token &Tok, const SourceManager &Sources) {
79  return StringRef(Sources.getCharacterData(Tok.getLocation()),
80  Tok.getLength());
81 }
82 
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 + " ").str();
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).str();
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 + " ").str();
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).str();
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
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::tidy::modernize::UseOverrideCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: UseOverrideCheck.cpp:27
clang::tidy::modernize::UseOverrideCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: UseOverrideCheck.cpp:83
clang::tidy::modernize::Message
constexpr llvm::StringLiteral Message
Definition: UseTrailingReturnTypeCheck.cpp:114
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:53
clang::tidy::modernize::getText
static StringRef getText(const Token &Tok, const SourceManager &Sources)
Definition: UseOverrideCheck.cpp:78
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:419
clang::ast_matchers
Definition: AbseilMatcher.h:14
ns1::ns2::A
@ A
Definition: CategoricalFeature.h:3
clang::tidy::modernize::UseOverrideCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: UseOverrideCheck.cpp:34
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:415
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:66
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
Name
Token Name
Definition: MacroToEnumCheck.cpp:89
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:121
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:120
clang::tidy::modernize::parseTokens
static SmallVector< Token, 16 > parseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result)
Definition: UseOverrideCheck.cpp:46
UseOverrideCheck.h