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