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