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 173