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 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::modernize { |
18 | |
19 | UseOverrideCheck::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 | |
28 | void 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 | |
37 | void 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'. |
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(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 | |
88 | static StringRef getText(const Token &Tok, const SourceManager &Sources) { |
89 | return {Sources.getCharacterData(SL: Tok.getLocation()), Tok.getLength()}; |
90 | } |
91 | |
92 | void 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 | |