1 //===--- UseOverrideCheck.cpp - clang-tidy --------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::modernize
{
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")) {}
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
);
37 void UseOverrideCheck::registerMatchers(MatchFinder
*Finder
) {
39 auto IgnoreDestructorMatcher
=
40 IgnoreDestructors
? cxxMethodDecl(unless(cxxDestructorDecl()))
42 auto IgnoreTemplateInstantiationsMatcher
=
43 IgnoreTemplateInstantiations
44 ? cxxMethodDecl(unless(ast_matchers::isTemplateInstantiation()))
46 Finder
->addMatcher(cxxMethodDecl(isOverride(),
47 IgnoreTemplateInstantiationsMatcher
,
48 IgnoreDestructorMatcher
)
53 // Re-lex the tokens to get precise locations to insert 'override' and remove
55 static SmallVector
<Token
, 16>
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
,
65 SmallVector
<Token
, 16> Tokens
;
68 while (!RawLexer
.LexFromRawLexer(Tok
)) {
69 if ((Tok
.is(tok::semi
) || Tok
.is(tok::l_brace
)) && NestedParens
== 0)
71 if (Sources
.isBeforeInTranslationUnit(Range
.getEnd(), Tok
.getLocation()))
73 if (Tok
.is(tok::l_paren
))
75 else if (Tok
.is(tok::r_paren
))
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());
83 Tokens
.push_back(Tok
);
88 static StringRef
getText(const Token
&Tok
, const SourceManager
&Sources
) {
89 return {Sources
.getCharacterData(Tok
.getLocation()), Tok
.getLength()};
92 void UseOverrideCheck::check(const MatchFinder::MatchResult
&Result
) {
93 const auto *Method
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("method");
94 const SourceManager
&Sources
= *Result
.SourceManager
;
96 ASTContext
&Context
= *Result
.Context
;
98 assert(Method
!= nullptr);
99 if (Method
->getInstantiatedFromMemberFunction() != nullptr)
100 Method
= Method
->getInstantiatedFromMemberFunction();
102 if (Method
->isImplicit() || Method
->getLocation().isMacroID() ||
103 Method
->isOutOfLine())
106 bool HasVirtual
= Method
->isVirtualAsWritten();
107 bool HasOverride
= Method
->getAttr
<OverrideAttr
>();
108 bool HasFinal
= Method
->getAttr
<FinalAttr
>();
110 bool OnlyVirtualSpecified
= HasVirtual
&& !HasOverride
&& !HasFinal
;
111 unsigned KeywordCount
= HasVirtual
+ HasOverride
+ HasFinal
;
113 if ((!OnlyVirtualSpecified
&& KeywordCount
== 1) ||
114 (!HasVirtual
&& HasOverride
&& HasFinal
&& AllowOverrideAndFinal
))
115 return; // Nothing to do.
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'";
123 StringRef Redundant
=
124 HasVirtual
? (HasOverride
&& HasFinal
&& !AllowOverrideAndFinal
125 ? "'virtual' and '%0' are"
128 StringRef Correct
= HasFinal
? "'%1'" : "'%0'";
130 Message
= (llvm::Twine(Redundant
) +
131 " redundant since the function is already declared " + Correct
)
135 auto Diag
= diag(Method
->getLocation(), Message
)
136 << OverrideSpelling
<< FinalSpelling
;
138 CharSourceRange FileRange
= Lexer::makeFileCharRange(
139 CharSourceRange::getTokenRange(Method
->getSourceRange()), Sources
,
142 if (!FileRange
.isValid())
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
148 SmallVector
<Token
, 16> Tokens
= parseTokens(FileRange
, Result
);
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();
156 for (Token T
: Tokens
) {
157 if (T
.is(tok::kw___attribute
) &&
158 !Sources
.isBeforeInTranslationUnit(T
.getLocation(), MethodLoc
)) {
159 InsertLoc
= T
.getLocation();
164 if (Method
->hasAttrs()) {
165 for (const clang::Attr
*A
: Method
->getAttrs()) {
166 if (!A
->isImplicit() && !A
->isInherited()) {
168 Sources
.getExpansionLoc(A
->getRange().getBegin());
169 if ((!InsertLoc
.isValid() ||
170 Sources
.isBeforeInTranslationUnit(Loc
, InsertLoc
)) &&
171 !Sources
.isBeforeInTranslationUnit(Loc
, MethodLoc
))
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();
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();
209 if (!InsertLoc
.isValid()) {
210 InsertLoc
= FileRange
.getEnd();
211 ReplacementText
= (" " + OverrideSpelling
).str();
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())
220 Diag
<< FixItHint::CreateInsertion(InsertLoc
, ReplacementText
);
223 if (HasFinal
&& HasOverride
&& !AllowOverrideAndFinal
) {
224 SourceLocation OverrideLoc
= Method
->getAttr
<OverrideAttr
>()->getLocation();
225 Diag
<< FixItHint::CreateRemoval(
226 CharSourceRange::getTokenRange(OverrideLoc
, OverrideLoc
));
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()));
245 } // namespace clang::tidy::modernize