• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AST_MATCHER(CXXMethodDecl,isStatic)24 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
25 
AST_MATCHER(CXXMethodDecl,hasTrivialBody)26 AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
27 
AST_MATCHER(CXXMethodDecl,isOverloadedOperator)28 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
29   return Node.isOverloadedOperator();
30 }
31 
AST_MATCHER(CXXRecordDecl,hasAnyDependentBases)32 AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
33   return Node.hasAnyDependentBases();
34 }
35 
AST_MATCHER(CXXMethodDecl,isTemplate)36 AST_MATCHER(CXXMethodDecl, isTemplate) {
37   return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
38 }
39 
AST_MATCHER(CXXMethodDecl,isDependentContext)40 AST_MATCHER(CXXMethodDecl, isDependentContext) {
41   return Node.isDependentContext();
42 }
43 
AST_MATCHER(CXXMethodDecl,isInsideMacroDefinition)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 
AST_MATCHER_P(CXXMethodDecl,hasCanonicalDecl,ast_matchers::internal::Matcher<CXXMethodDecl>,InnerMatcher)53 AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
54               ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
55   return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
56 }
57 
AST_MATCHER(CXXMethodDecl,usesThis)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 
registerMatchers(MatchFinder * Finder)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.
getStringFromRange(SourceManager & SourceMgr,const LangOptions & LangOpts,SourceRange Range)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 
getLocationOfConst(const TypeSourceInfo * TSI,SourceManager & SourceMgr,const LangOptions & LangOpts)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 
check(const MatchFinder::MatchResult & Result)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