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