| 1 | //===--- ConvertMemberFunctionsToStatic.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 "ConvertMemberFunctionsToStatic.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/AST/DeclCXX.h" |
| 12 | #include "clang/AST/RecursiveASTVisitor.h" |
| 13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 14 | #include "clang/Basic/SourceLocation.h" |
| 15 | #include "clang/Lex/Lexer.h" |
| 16 | |
| 17 | using namespace clang::ast_matchers; |
| 18 | |
| 19 | namespace clang::tidy::readability { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } |
| 24 | |
| 25 | AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } |
| 26 | |
| 27 | AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { |
| 28 | return Node.isOverloadedOperator(); |
| 29 | } |
| 30 | |
| 31 | AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { |
| 32 | return Node.hasAnyDependentBases(); |
| 33 | } |
| 34 | |
| 35 | AST_MATCHER(CXXMethodDecl, isTemplate) { |
| 36 | return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; |
| 37 | } |
| 38 | |
| 39 | AST_MATCHER(CXXMethodDecl, isDependentContext) { |
| 40 | return Node.isDependentContext(); |
| 41 | } |
| 42 | |
| 43 | AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) { |
| 44 | const ASTContext &Ctxt = Finder->getASTContext(); |
| 45 | return clang::Lexer::makeFileCharRange( |
| 46 | Range: clang::CharSourceRange::getCharRange( |
| 47 | Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), |
| 48 | SM: Ctxt.getSourceManager(), LangOpts: Ctxt.getLangOpts()) |
| 49 | .isInvalid(); |
| 50 | } |
| 51 | |
| 52 | AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, |
| 53 | ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { |
| 54 | return InnerMatcher.matches(Node: *Node.getCanonicalDecl(), Finder, Builder); |
| 55 | } |
| 56 | |
| 57 | AST_MATCHER(CXXMethodDecl, usesThis) { |
| 58 | class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { |
| 59 | public: |
| 60 | bool Used = false; |
| 61 | |
| 62 | bool VisitCXXThisExpr(const CXXThisExpr *E) { |
| 63 | Used = true; |
| 64 | return false; // Stop traversal. |
| 65 | } |
| 66 | |
| 67 | // If we enter a class declaration, don't traverse into it as any usages of |
| 68 | // `this` will correspond to the nested class. |
| 69 | bool TraverseCXXRecordDecl(CXXRecordDecl *RD) { return true; } |
| 70 | |
| 71 | } UsageOfThis; |
| 72 | |
| 73 | // TraverseStmt does not modify its argument. |
| 74 | UsageOfThis.TraverseStmt(S: const_cast<Stmt *>(Node.getBody())); |
| 75 | |
| 76 | return UsageOfThis.Used; |
| 77 | } |
| 78 | |
| 79 | } // namespace |
| 80 | |
| 81 | void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { |
| 82 | Finder->addMatcher( |
| 83 | NodeMatch: cxxMethodDecl( |
| 84 | isDefinition(), isUserProvided(), |
| 85 | unless(anyOf( |
| 86 | isExpansionInSystemHeader(), isVirtual(), isStatic(), |
| 87 | hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), |
| 88 | cxxDestructorDecl(), cxxConversionDecl(), |
| 89 | isExplicitObjectMemberFunction(), isTemplate(), |
| 90 | isDependentContext(), |
| 91 | ofClass(InnerMatcher: anyOf( |
| 92 | isLambda(), |
| 93 | hasAnyDependentBases()) // Method might become virtual |
| 94 | // depending on template base class. |
| 95 | ), |
| 96 | isInsideMacroDefinition(), |
| 97 | hasCanonicalDecl(InnerMatcher: isInsideMacroDefinition()), usesThis()))) |
| 98 | .bind(ID: "x" ), |
| 99 | Action: this); |
| 100 | } |
| 101 | |
| 102 | /// Obtain the original source code text from a SourceRange. |
| 103 | static StringRef getStringFromRange(SourceManager &SourceMgr, |
| 104 | const LangOptions &LangOpts, |
| 105 | SourceRange Range) { |
| 106 | if (SourceMgr.getFileID(SpellingLoc: Range.getBegin()) != |
| 107 | SourceMgr.getFileID(SpellingLoc: Range.getEnd())) |
| 108 | return {}; |
| 109 | |
| 110 | return Lexer::getSourceText(Range: CharSourceRange(Range, true), SM: SourceMgr, |
| 111 | LangOpts); |
| 112 | } |
| 113 | |
| 114 | static SourceRange getLocationOfConst(const TypeSourceInfo *TSI, |
| 115 | SourceManager &SourceMgr, |
| 116 | const LangOptions &LangOpts) { |
| 117 | assert(TSI); |
| 118 | const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); |
| 119 | assert(FTL); |
| 120 | |
| 121 | SourceRange Range{FTL.getRParenLoc().getLocWithOffset(Offset: 1), |
| 122 | FTL.getLocalRangeEnd()}; |
| 123 | // Inside Range, there might be other keywords and trailing return types. |
| 124 | // Find the exact position of "const". |
| 125 | StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); |
| 126 | size_t Offset = Text.find(Str: "const" ); |
| 127 | if (Offset == StringRef::npos) |
| 128 | return {}; |
| 129 | |
| 130 | SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); |
| 131 | return {Start, Start.getLocWithOffset(Offset: strlen(s: "const" ) - 1)}; |
| 132 | } |
| 133 | |
| 134 | void ConvertMemberFunctionsToStatic::check( |
| 135 | const MatchFinder::MatchResult &Result) { |
| 136 | const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "x" ); |
| 137 | |
| 138 | // TODO: For out-of-line declarations, don't modify the source if the header |
| 139 | // is excluded by the -header-filter option. |
| 140 | DiagnosticBuilder Diag = |
| 141 | diag(Definition->getLocation(), "method %0 can be made static" ) |
| 142 | << Definition; |
| 143 | |
| 144 | // TODO: Would need to remove those in a fix-it. |
| 145 | if (Definition->getMethodQualifiers().hasVolatile() || |
| 146 | Definition->getMethodQualifiers().hasRestrict() || |
| 147 | Definition->getRefQualifier() != RQ_None) |
| 148 | return; |
| 149 | |
| 150 | const CXXMethodDecl *Declaration = Definition->getCanonicalDecl(); |
| 151 | |
| 152 | if (Definition->isConst()) { |
| 153 | // Make sure that we either remove 'const' on both declaration and |
| 154 | // definition or emit no fix-it at all. |
| 155 | SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(), |
| 156 | *Result.SourceManager, |
| 157 | Result.Context->getLangOpts()); |
| 158 | |
| 159 | if (DefConst.isInvalid()) |
| 160 | return; |
| 161 | |
| 162 | if (Declaration != Definition) { |
| 163 | SourceRange DeclConst = getLocationOfConst( |
| 164 | Declaration->getTypeSourceInfo(), *Result.SourceManager, |
| 165 | Result.Context->getLangOpts()); |
| 166 | |
| 167 | if (DeclConst.isInvalid()) |
| 168 | return; |
| 169 | Diag << FixItHint::CreateRemoval(RemoveRange: DeclConst); |
| 170 | } |
| 171 | |
| 172 | // Remove existing 'const' from both declaration and definition. |
| 173 | Diag << FixItHint::CreateRemoval(RemoveRange: DefConst); |
| 174 | } |
| 175 | Diag << FixItHint::CreateInsertion(InsertionLoc: Declaration->getBeginLoc(), Code: "static " ); |
| 176 | } |
| 177 | |
| 178 | } // namespace clang::tidy::readability |
| 179 | |