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 "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::modernize {
18
19UseOverrideCheck::UseOverrideCheck(StringRef Name, ClangTidyContext *Context)
20 : ClangTidyCheck(Name, Context),
21 IgnoreDestructors(Options.get(LocalName: "IgnoreDestructors", Default: false)),
22 IgnoreTemplateInstantiations(
23 Options.get(LocalName: "IgnoreTemplateInstantiations", Default: false)),
24 AllowOverrideAndFinal(Options.get(LocalName: "AllowOverrideAndFinal", Default: false)),
25 OverrideSpelling(Options.get(LocalName: "OverrideSpelling", Default: "override")),
26 FinalSpelling(Options.get(LocalName: "FinalSpelling", Default: "final")) {}
27
28void UseOverrideCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
29 Options.store(Options&: Opts, LocalName: "IgnoreDestructors", Value: IgnoreDestructors);
30 Options.store(Options&: Opts, LocalName: "IgnoreTemplateInstantiations",
31 Value: IgnoreTemplateInstantiations);
32 Options.store(Options&: Opts, LocalName: "AllowOverrideAndFinal", Value: AllowOverrideAndFinal);
33 Options.store(Options&: Opts, LocalName: "OverrideSpelling", Value: OverrideSpelling);
34 Options.store(Options&: Opts, LocalName: "FinalSpelling", Value: FinalSpelling);
35}
36
37void UseOverrideCheck::registerMatchers(MatchFinder *Finder) {
38
39 auto IgnoreDestructorMatcher =
40 IgnoreDestructors ? cxxMethodDecl(unless(cxxDestructorDecl()))
41 : cxxMethodDecl();
42 auto IgnoreTemplateInstantiationsMatcher =
43 IgnoreTemplateInstantiations
44 ? cxxMethodDecl(unless(ast_matchers::isTemplateInstantiation()))
45 : cxxMethodDecl();
46 Finder->addMatcher(NodeMatch: cxxMethodDecl(isOverride(),
47 IgnoreTemplateInstantiationsMatcher,
48 IgnoreDestructorMatcher)
49 .bind(ID: "method"),
50 Action: this);
51}
52
53// Re-lex the tokens to get precise locations to insert 'override' and remove
54// 'virtual'.
55static SmallVector<Token, 16>
56parseTokens(CharSourceRange Range, const MatchFinder::MatchResult &Result) {
57 const SourceManager &Sources = *Result.SourceManager;
58 std::pair<FileID, unsigned> LocInfo =
59 Sources.getDecomposedLoc(Loc: Range.getBegin());
60 StringRef File = Sources.getBufferData(FID: LocInfo.first);
61 const char *TokenBegin = File.data() + LocInfo.second;
62 Lexer RawLexer(Sources.getLocForStartOfFile(FID: LocInfo.first),
63 Result.Context->getLangOpts(), File.begin(), TokenBegin,
64 File.end());
65 SmallVector<Token, 16> Tokens;
66 Token Tok;
67 int NestedParens = 0;
68 while (!RawLexer.LexFromRawLexer(Result&: Tok)) {
69 if ((Tok.is(K: tok::semi) || Tok.is(K: tok::l_brace)) && NestedParens == 0)
70 break;
71 if (Sources.isBeforeInTranslationUnit(LHS: Range.getEnd(), RHS: Tok.getLocation()))
72 break;
73 if (Tok.is(K: tok::l_paren))
74 ++NestedParens;
75 else if (Tok.is(K: tok::r_paren))
76 --NestedParens;
77 if (Tok.is(K: tok::raw_identifier)) {
78 IdentifierInfo &Info = Result.Context->Idents.get(Name: StringRef(
79 Sources.getCharacterData(SL: Tok.getLocation()), Tok.getLength()));
80 Tok.setIdentifierInfo(&Info);
81 Tok.setKind(Info.getTokenID());
82 }
83 Tokens.push_back(Elt: Tok);
84 }
85 return Tokens;
86}
87
88static StringRef getText(const Token &Tok, const SourceManager &Sources) {
89 return {Sources.getCharacterData(SL: Tok.getLocation()), Tok.getLength()};
90}
91
92void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) {
93 const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>(ID: "method");
94 const SourceManager &Sources = *Result.SourceManager;
95
96 ASTContext &Context = *Result.Context;
97
98 assert(Method != nullptr);
99 if (Method->getInstantiatedFromMemberFunction() != nullptr)
100 Method = Method->getInstantiatedFromMemberFunction();
101
102 if (Method->isImplicit() || Method->getLocation().isMacroID() ||
103 Method->isOutOfLine())
104 return;
105
106 bool HasVirtual = Method->isVirtualAsWritten();
107 bool HasOverride = Method->getAttr<OverrideAttr>();
108 bool HasFinal = Method->getAttr<FinalAttr>();
109
110 bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal;
111 unsigned KeywordCount = HasVirtual + HasOverride + HasFinal;
112
113 if ((!OnlyVirtualSpecified && KeywordCount == 1) ||
114 (!HasVirtual && HasOverride && HasFinal && AllowOverrideAndFinal))
115 return; // Nothing to do.
116
117 std::string Message;
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'";
122 } else {
123 StringRef Redundant =
124 HasVirtual ? (HasOverride && HasFinal && !AllowOverrideAndFinal
125 ? "'virtual' and '%0' are"
126 : "'virtual' is")
127 : "'%0' is";
128 StringRef Correct = HasFinal ? "'%1'" : "'%0'";
129
130 Message = (llvm::Twine(Redundant) +
131 " redundant since the function is already declared " + Correct)
132 .str();
133 }
134
135 auto Diag = diag(Method->getLocation(), Message)
136 << OverrideSpelling << FinalSpelling;
137
138 CharSourceRange FileRange = Lexer::makeFileCharRange(
139 Range: CharSourceRange::getTokenRange(R: Method->getSourceRange()), SM: Sources,
140 LangOpts: getLangOpts());
141
142 if (!FileRange.isValid())
143 return;
144
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
147 // FunctionDecl.
148 SmallVector<Token, 16> Tokens = parseTokens(Range: FileRange, Result);
149
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();
155
156 for (Token T : Tokens) {
157 if (T.is(K: tok::kw___attribute) &&
158 !Sources.isBeforeInTranslationUnit(LHS: T.getLocation(), RHS: MethodLoc)) {
159 InsertLoc = T.getLocation();
160 break;
161 }
162 }
163
164 if (Method->hasAttrs()) {
165 for (const clang::Attr *A : Method->getAttrs()) {
166 if (!A->isImplicit() && !A->isInherited()) {
167 SourceLocation Loc =
168 Sources.getExpansionLoc(A->getRange().getBegin());
169 if ((!InsertLoc.isValid() ||
170 Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) &&
171 !Sources.isBeforeInTranslationUnit(Loc, MethodLoc))
172 InsertLoc = Loc;
173 }
174 }
175 }
176
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(x: 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(K: tok::kw_try))
188 LastTokenIter = std::prev(x: LastTokenIter);
189 InsertLoc = LastTokenIter->getEndLoc();
190 }
191
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(Tok: Tokens.back(), Sources) == "0" ||
198 Tokens.back().is(K: tok::kw_default) ||
199 Tokens.back().is(K: tok::kw_delete)) &&
200 getText(Tok: 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(Tok: Tokens.back(), Sources) == "ABSTRACT")
206 InsertLoc = Tokens.back().getLocation();
207 }
208
209 if (!InsertLoc.isValid()) {
210 InsertLoc = FileRange.getEnd();
211 ReplacementText = (" " + OverrideSpelling).str();
212 }
213
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(Name: OverrideSpelling).hasMacroDefinition())
218 return;
219
220 Diag << FixItHint::CreateInsertion(InsertionLoc: InsertLoc, Code: ReplacementText);
221 }
222
223 if (HasFinal && HasOverride && !AllowOverrideAndFinal) {
224 SourceLocation OverrideLoc = Method->getAttr<OverrideAttr>()->getLocation();
225 Diag << FixItHint::CreateRemoval(
226 RemoveRange: CharSourceRange::getTokenRange(B: OverrideLoc, E: OverrideLoc));
227 }
228
229 if (HasVirtual) {
230 for (Token Tok : Tokens) {
231 if (Tok.is(K: tok::kw_virtual)) {
232 std::optional<Token> NextToken =
233 utils::lexer::findNextTokenIncludingComments(
234 Start: Tok.getEndLoc(), SM: Sources, LangOpts: getLangOpts());
235 if (NextToken.has_value()) {
236 Diag << FixItHint::CreateRemoval(RemoveRange: CharSourceRange::getCharRange(
237 B: Tok.getLocation(), E: NextToken->getLocation()));
238 break;
239 }
240 }
241 }
242 }
243}
244
245} // namespace clang::tidy::modernize
246

source code of clang-tools-extra/clang-tidy/modernize/UseOverrideCheck.cpp