| 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 | |