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