|
| 1 | +//===--- ConvertMemberFunctionsToStatic.cpp - clang-tidy ------------------===// |
| 2 | +// |
| 3 | +// The LLVM Compiler Infrastructure |
| 4 | +// |
| 5 | +// This file is distributed under the University of Illinois Open Source |
| 6 | +// License. See LICENSE.TXT for details. |
| 7 | +// |
| 8 | +//===----------------------------------------------------------------------===// |
| 9 | + |
| 10 | +#include "ConvertMemberFunctionsToStatic.h" |
| 11 | +#include "clang/AST/ASTContext.h" |
| 12 | +#include "clang/AST/DeclCXX.h" |
| 13 | +#include "clang/AST/RecursiveASTVisitor.h" |
| 14 | +#include "clang/ASTMatchers/ASTMatchFinder.h" |
| 15 | +#include "clang/Basic/SourceLocation.h" |
| 16 | + |
| 17 | +using namespace clang::ast_matchers; |
| 18 | + |
| 19 | +namespace clang { |
| 20 | +namespace tidy { |
| 21 | +namespace readability { |
| 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 | + clang::CharSourceRange::getCharRange( |
| 47 | + Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), |
| 48 | + Ctxt.getSourceManager(), Ctxt.getLangOpts()) |
| 49 | + .isInvalid(); |
| 50 | +} |
| 51 | + |
| 52 | +AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, |
| 53 | + ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { |
| 54 | + return InnerMatcher.matches(*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 | + } UsageOfThis; |
| 67 | + |
| 68 | + // TraverseStmt does not modify its argument. |
| 69 | + UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody())); |
| 70 | + |
| 71 | + return UsageOfThis.Used; |
| 72 | +} |
| 73 | + |
| 74 | +void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { |
| 75 | + Finder->addMatcher( |
| 76 | + cxxMethodDecl( |
| 77 | + isDefinition(), isUserProvided(), |
| 78 | + unless(anyOf( |
| 79 | + isExpansionInSystemHeader(), isVirtual(), isStatic(), |
| 80 | + hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), |
| 81 | + cxxDestructorDecl(), cxxConversionDecl(), isTemplate(), |
| 82 | + isDependentContext(), |
| 83 | + ofClass(anyOf( |
| 84 | + isLambda(), |
| 85 | + hasAnyDependentBases()) // Method might become virtual |
| 86 | + // depending on template base class. |
| 87 | + ), |
| 88 | + isInsideMacroDefinition(), |
| 89 | + hasCanonicalDecl(isInsideMacroDefinition()), usesThis()))) |
| 90 | + .bind("x"), |
| 91 | + this); |
| 92 | +} |
| 93 | + |
| 94 | +/// \brief Obtain the original source code text from a SourceRange. |
| 95 | +static StringRef getStringFromRange(SourceManager &SourceMgr, |
| 96 | + const LangOptions &LangOpts, |
| 97 | + SourceRange Range) { |
| 98 | + if (SourceMgr.getFileID(Range.getBegin()) != |
| 99 | + SourceMgr.getFileID(Range.getEnd())) |
| 100 | + return {}; |
| 101 | + |
| 102 | + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, |
| 103 | + LangOpts); |
| 104 | +} |
| 105 | + |
| 106 | +static SourceRange getLocationOfConst(const TypeSourceInfo *TSI, |
| 107 | + SourceManager &SourceMgr, |
| 108 | + const LangOptions &LangOpts) { |
| 109 | + assert(TSI); |
| 110 | + const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); |
| 111 | + assert(FTL); |
| 112 | + |
| 113 | + SourceRange Range{FTL.getRParenLoc().getLocWithOffset(1), |
| 114 | + FTL.getLocalRangeEnd()}; |
| 115 | + // Inside Range, there might be other keywords and trailing return types. |
| 116 | + // Find the exact position of "const". |
| 117 | + StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); |
| 118 | + size_t Offset = Text.find("const"); |
| 119 | + if (Offset == StringRef::npos) |
| 120 | + return {}; |
| 121 | + |
| 122 | + SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); |
| 123 | + return {Start, Start.getLocWithOffset(strlen("const") - 1)}; |
| 124 | +} |
| 125 | + |
| 126 | +void ConvertMemberFunctionsToStatic::check( |
| 127 | + const MatchFinder::MatchResult &Result) { |
| 128 | + const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); |
| 129 | + |
| 130 | + // TODO: For out-of-line declarations, don't modify the source if the header |
| 131 | + // is excluded by the -header-filter option. |
| 132 | + DiagnosticBuilder Diag = |
| 133 | + diag(Definition->getLocation(), "method %0 can be made static") |
| 134 | + << Definition; |
| 135 | + |
| 136 | + // TODO: Would need to remove those in a fix-it. |
| 137 | + if (Definition->getMethodQualifiers().hasVolatile() || |
| 138 | + Definition->getMethodQualifiers().hasRestrict() || |
| 139 | + Definition->getRefQualifier() != RQ_None) |
| 140 | + return; |
| 141 | + |
| 142 | + const CXXMethodDecl *Declaration = Definition->getCanonicalDecl(); |
| 143 | + |
| 144 | + if (Definition->isConst()) { |
| 145 | + // Make sure that we either remove 'const' on both declaration and |
| 146 | + // definition or emit no fix-it at all. |
| 147 | + SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(), |
| 148 | + *Result.SourceManager, |
| 149 | + Result.Context->getLangOpts()); |
| 150 | + |
| 151 | + if (DefConst.isInvalid()) |
| 152 | + return; |
| 153 | + |
| 154 | + if (Declaration != Definition) { |
| 155 | + SourceRange DeclConst = getLocationOfConst( |
| 156 | + Declaration->getTypeSourceInfo(), *Result.SourceManager, |
| 157 | + Result.Context->getLangOpts()); |
| 158 | + |
| 159 | + if (DeclConst.isInvalid()) |
| 160 | + return; |
| 161 | + Diag << FixItHint::CreateRemoval(DeclConst); |
| 162 | + } |
| 163 | + |
| 164 | + // Remove existing 'const' from both declaration and definition. |
| 165 | + Diag << FixItHint::CreateRemoval(DefConst); |
| 166 | + } |
| 167 | + Diag << FixItHint::CreateInsertion(Declaration->getBeginLoc(), "static "); |
| 168 | +} |
| 169 | + |
| 170 | +} // namespace readability |
| 171 | +} // namespace tidy |
| 172 | +} // namespace clang |
0 commit comments