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