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