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 "clang/AST/ASTContext.h" |
11 | #include "clang/AST/DeclCXX.h" |
12 | #include "clang/AST/RecursiveASTVisitor.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | #include "clang/Basic/SourceLocation.h" |
15 | #include "clang/Lex/Lexer.h" |
16 | |
17 | using namespace clang::ast_matchers; |
18 | |
19 | namespace clang::tidy::readability { |
20 | |
21 | namespace { |
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 | Range: clang::CharSourceRange::getCharRange( |
47 | Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), |
48 | SM: Ctxt.getSourceManager(), LangOpts: Ctxt.getLangOpts()) |
49 | .isInvalid(); |
50 | } |
51 | |
52 | AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, |
53 | ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { |
54 | return InnerMatcher.matches(Node: *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 | |
67 | // If we enter a class declaration, don't traverse into it as any usages of |
68 | // `this` will correspond to the nested class. |
69 | bool TraverseCXXRecordDecl(CXXRecordDecl *RD) { return true; } |
70 | |
71 | } UsageOfThis; |
72 | |
73 | // TraverseStmt does not modify its argument. |
74 | UsageOfThis.TraverseStmt(S: const_cast<Stmt *>(Node.getBody())); |
75 | |
76 | return UsageOfThis.Used; |
77 | } |
78 | |
79 | } // namespace |
80 | |
81 | void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { |
82 | Finder->addMatcher( |
83 | NodeMatch: cxxMethodDecl( |
84 | isDefinition(), isUserProvided(), |
85 | unless(anyOf( |
86 | isExpansionInSystemHeader(), isVirtual(), isStatic(), |
87 | hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), |
88 | cxxDestructorDecl(), cxxConversionDecl(), |
89 | isExplicitObjectMemberFunction(), isTemplate(), |
90 | isDependentContext(), |
91 | ofClass(InnerMatcher: anyOf( |
92 | isLambda(), |
93 | hasAnyDependentBases()) // Method might become virtual |
94 | // depending on template base class. |
95 | ), |
96 | isInsideMacroDefinition(), |
97 | hasCanonicalDecl(InnerMatcher: isInsideMacroDefinition()), usesThis()))) |
98 | .bind(ID: "x" ), |
99 | Action: this); |
100 | } |
101 | |
102 | /// Obtain the original source code text from a SourceRange. |
103 | static StringRef getStringFromRange(SourceManager &SourceMgr, |
104 | const LangOptions &LangOpts, |
105 | SourceRange Range) { |
106 | if (SourceMgr.getFileID(SpellingLoc: Range.getBegin()) != |
107 | SourceMgr.getFileID(SpellingLoc: Range.getEnd())) |
108 | return {}; |
109 | |
110 | return Lexer::getSourceText(Range: CharSourceRange(Range, true), SM: SourceMgr, |
111 | LangOpts); |
112 | } |
113 | |
114 | static SourceRange getLocationOfConst(const TypeSourceInfo *TSI, |
115 | SourceManager &SourceMgr, |
116 | const LangOptions &LangOpts) { |
117 | assert(TSI); |
118 | const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); |
119 | assert(FTL); |
120 | |
121 | SourceRange Range{FTL.getRParenLoc().getLocWithOffset(Offset: 1), |
122 | FTL.getLocalRangeEnd()}; |
123 | // Inside Range, there might be other keywords and trailing return types. |
124 | // Find the exact position of "const". |
125 | StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); |
126 | size_t Offset = Text.find(Str: "const" ); |
127 | if (Offset == StringRef::npos) |
128 | return {}; |
129 | |
130 | SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); |
131 | return {Start, Start.getLocWithOffset(Offset: strlen(s: "const" ) - 1)}; |
132 | } |
133 | |
134 | void ConvertMemberFunctionsToStatic::check( |
135 | const MatchFinder::MatchResult &Result) { |
136 | const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "x" ); |
137 | |
138 | // TODO: For out-of-line declarations, don't modify the source if the header |
139 | // is excluded by the -header-filter option. |
140 | DiagnosticBuilder Diag = |
141 | diag(Definition->getLocation(), "method %0 can be made static" ) |
142 | << Definition; |
143 | |
144 | // TODO: Would need to remove those in a fix-it. |
145 | if (Definition->getMethodQualifiers().hasVolatile() || |
146 | Definition->getMethodQualifiers().hasRestrict() || |
147 | Definition->getRefQualifier() != RQ_None) |
148 | return; |
149 | |
150 | const CXXMethodDecl *Declaration = Definition->getCanonicalDecl(); |
151 | |
152 | if (Definition->isConst()) { |
153 | // Make sure that we either remove 'const' on both declaration and |
154 | // definition or emit no fix-it at all. |
155 | SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(), |
156 | *Result.SourceManager, |
157 | Result.Context->getLangOpts()); |
158 | |
159 | if (DefConst.isInvalid()) |
160 | return; |
161 | |
162 | if (Declaration != Definition) { |
163 | SourceRange DeclConst = getLocationOfConst( |
164 | Declaration->getTypeSourceInfo(), *Result.SourceManager, |
165 | Result.Context->getLangOpts()); |
166 | |
167 | if (DeclConst.isInvalid()) |
168 | return; |
169 | Diag << FixItHint::CreateRemoval(RemoveRange: DeclConst); |
170 | } |
171 | |
172 | // Remove existing 'const' from both declaration and definition. |
173 | Diag << FixItHint::CreateRemoval(RemoveRange: DefConst); |
174 | } |
175 | Diag << FixItHint::CreateInsertion(InsertionLoc: Declaration->getBeginLoc(), Code: "static " ); |
176 | } |
177 | |
178 | } // namespace clang::tidy::readability |
179 | |