1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <fstream>
14 #include <set>
15 #include <unordered_map>
16
17
18 #include "clang/AST/Attr.h"
19
20 #include "plugin.hxx"
21 #include "compat.hxx"
22
23 /**
24 Find methods that are only called from inside their own class, and are only called from one spot.
25 They are candidates to be removed and have their code inlined into the call site.
26
27
28 TODO if a method has only one call-site, and that call site is inside a constructor
29 then it's probably worth inlining, since it's probably an old method that was intended to be shared amongst
30 multiple constructors
31 */
32
33 namespace {
34
35 struct MyFuncInfo
36 {
37 std::string access;
38 std::string returnType;
39 std::string nameAndParams;
40 std::string sourceLocation;
41
42 };
operator <(const MyFuncInfo & lhs,const MyFuncInfo & rhs)43 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
44 {
45 return std::tie(lhs.returnType, lhs.nameAndParams)
46 < std::tie(rhs.returnType, rhs.nameAndParams);
47 }
48
49 // try to limit the voluminous output a little
50
51 static std::set<std::pair<std::string, MyFuncInfo>> calledFromSet;
52 static std::set<MyFuncInfo> definitionSet;
53 static std::set<MyFuncInfo> calledFromOutsideSet;
54 static std::set<MyFuncInfo> largeFunctionSet;
55 static std::set<MyFuncInfo> addressOfSet;
56
57
58 class ExpandableMethods:
59 public RecursiveASTVisitor<ExpandableMethods>, public loplugin::Plugin
60 {
61 public:
ExpandableMethods(loplugin::InstantiationData const & data)62 explicit ExpandableMethods(loplugin::InstantiationData const & data):
63 Plugin(data) {}
64
run()65 virtual void run() override
66 {
67 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
68
69 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
70 // writing to the same logfile
71
72 std::string output;
73 for (const MyFuncInfo & s : definitionSet)
74 output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
75 for (const MyFuncInfo & s : calledFromOutsideSet)
76 output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
77 for (const std::pair<std::string,MyFuncInfo> & s : calledFromSet)
78 output += "calledFrom:\t" + s.first
79 + "\t" + s.second.returnType + "\t" + s.second.nameAndParams + "\n";
80 for (const MyFuncInfo & s : largeFunctionSet)
81 output += "large:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
82 for (const MyFuncInfo & s : addressOfSet)
83 output += "addrof:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
84 std::ofstream myfile;
85 myfile.open( WORKDIR "/loplugin.expandablemethods.log", std::ios::app | std::ios::out);
86 myfile << output;
87 myfile.close();
88 }
89
shouldVisitTemplateInstantiations() const90 bool shouldVisitTemplateInstantiations () const { return true; }
shouldVisitImplicitCode() const91 bool shouldVisitImplicitCode() const { return true; }
92
93 bool VisitFunctionDecl( const FunctionDecl* );
94 bool VisitDeclRefExpr( const DeclRefExpr* );
95 bool VisitMemberExpr( const MemberExpr* );
96 // interception methods for FunctionDecl and all its subclasses
97 bool TraverseFunctionDecl( FunctionDecl* );
98 bool TraverseCXXMethodDecl( CXXMethodDecl* );
99 bool TraverseCXXConstructorDecl( CXXConstructorDecl* );
100 bool TraverseCXXConversionDecl( CXXConversionDecl* );
101 bool TraverseCXXDestructorDecl( CXXDestructorDecl* );
102
103 private:
104 MyFuncInfo niceName(const FunctionDecl* functionDecl);
105 std::string toString(SourceLocation loc);
106 void functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr );
107 bool isCalleeFunctionInteresting( const FunctionDecl* );
108
109 // I use traverse and a member variable because I cannot find a reliable way of walking back up the AST tree using the parentStmt() stuff
110 std::vector<const FunctionDecl*> maTraversingFunctions;
111 };
112
niceName(const FunctionDecl * functionDecl)113 MyFuncInfo ExpandableMethods::niceName(const FunctionDecl* functionDecl)
114 {
115 if (functionDecl->getInstantiatedFromMemberFunction())
116 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
117 #if CLANG_VERSION < 90000
118 else if (functionDecl->getClassScopeSpecializationPattern())
119 functionDecl = functionDecl->getClassScopeSpecializationPattern();
120 #endif
121 else if (functionDecl->getTemplateInstantiationPattern())
122 functionDecl = functionDecl->getTemplateInstantiationPattern();
123
124 MyFuncInfo aInfo;
125 switch (functionDecl->getAccess())
126 {
127 case AS_public: aInfo.access = "public"; break;
128 case AS_private: aInfo.access = "private"; break;
129 case AS_protected: aInfo.access = "protected"; break;
130 default: aInfo.access = "unknown"; break;
131 }
132 if (!isa<CXXConstructorDecl>(functionDecl)) {
133 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
134 } else {
135 aInfo.returnType = "";
136 }
137
138 if (isa<CXXMethodDecl>(functionDecl)) {
139 const CXXRecordDecl* recordDecl = dyn_cast<CXXMethodDecl>(functionDecl)->getParent();
140 aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
141 aInfo.nameAndParams += "::";
142 }
143 aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
144 bool bFirst = true;
145 for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
146 if (bFirst)
147 bFirst = false;
148 else
149 aInfo.nameAndParams += ",";
150 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
151 }
152 aInfo.nameAndParams += ")";
153 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
154 aInfo.nameAndParams += " const";
155 }
156
157 aInfo.sourceLocation = toString( functionDecl->getLocation() );
158
159 return aInfo;
160 }
161
toString(SourceLocation loc)162 std::string ExpandableMethods::toString(SourceLocation loc)
163 {
164 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
165 StringRef name = getFilenameOfLocation(expansionLoc);
166 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
167 loplugin::normalizeDotDotInFilePath(sourceLocation);
168 return sourceLocation;
169 }
170
VisitFunctionDecl(const FunctionDecl * functionDecl)171 bool ExpandableMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
172 {
173 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
174 if (!isCalleeFunctionInteresting(canonicalFunctionDecl)) {
175 return true;
176 }
177 definitionSet.insert(niceName(canonicalFunctionDecl));
178
179 if (functionDecl->doesThisDeclarationHaveABody()) {
180 bool bLargeFunction = false;
181 if (const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(functionDecl->getBody())) {
182 if (compoundStmt->size() > 1) {
183 bLargeFunction = true;
184 }
185 if (!bLargeFunction) {
186 auto s1 = compiler.getSourceManager().getCharacterData(compoundStmt->getLBracLoc());
187 auto s2 = compiler.getSourceManager().getCharacterData(compoundStmt->getRBracLoc());
188 bLargeFunction = (s2 - s1) > 40;
189 // any function that uses a parameter more than once
190 if (!bLargeFunction) {
191 StringRef bodyText(s1, s2-s1);
192 for (const ParmVarDecl* param : functionDecl->parameters()) {
193 StringRef name = param->getName();
194 if (name.empty())
195 continue;
196 size_t idx = bodyText.find(name);
197 if (idx != StringRef::npos && bodyText.find(name, idx+1) != StringRef::npos) {
198 bLargeFunction = true;
199 break;
200 }
201 }
202 }
203 }
204 }
205 if (bLargeFunction) {
206 largeFunctionSet.insert(niceName(canonicalFunctionDecl));
207 }
208 }
209 return true;
210 }
211
TraverseFunctionDecl(FunctionDecl * p)212 bool ExpandableMethods::TraverseFunctionDecl( FunctionDecl* p )
213 {
214 maTraversingFunctions.push_back(p);
215 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(p);
216 maTraversingFunctions.pop_back();
217 return ret;
218 }
TraverseCXXMethodDecl(CXXMethodDecl * p)219 bool ExpandableMethods::TraverseCXXMethodDecl( CXXMethodDecl* p )
220 {
221 maTraversingFunctions.push_back(p);
222 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(p);
223 maTraversingFunctions.pop_back();
224 return ret;
225 }
TraverseCXXConstructorDecl(CXXConstructorDecl * p)226 bool ExpandableMethods::TraverseCXXConstructorDecl( CXXConstructorDecl* p )
227 {
228 maTraversingFunctions.push_back(p);
229 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(p);
230 maTraversingFunctions.pop_back();
231 return ret;
232 }
TraverseCXXConversionDecl(CXXConversionDecl * p)233 bool ExpandableMethods::TraverseCXXConversionDecl( CXXConversionDecl* p )
234 {
235 maTraversingFunctions.push_back(p);
236 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(p);
237 maTraversingFunctions.pop_back();
238 return ret;
239 }
TraverseCXXDestructorDecl(CXXDestructorDecl * p)240 bool ExpandableMethods::TraverseCXXDestructorDecl( CXXDestructorDecl* p )
241 {
242 maTraversingFunctions.push_back(p);
243 bool ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(p);
244 maTraversingFunctions.pop_back();
245 return ret;
246 }
247
VisitMemberExpr(const MemberExpr * memberExpr)248 bool ExpandableMethods::VisitMemberExpr( const MemberExpr* memberExpr )
249 {
250 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(memberExpr->getMemberDecl());
251 if (functionDecl) {
252 functionTouchedFromExpr(functionDecl, memberExpr);
253 }
254 return true;
255 }
256
VisitDeclRefExpr(const DeclRefExpr * declRefExpr)257 bool ExpandableMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
258 {
259 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
260 if (functionDecl) {
261 functionTouchedFromExpr(functionDecl, declRefExpr);
262 }
263 return true;
264 }
265
functionTouchedFromExpr(const FunctionDecl * calleeFunctionDecl,const Expr * expr)266 void ExpandableMethods::functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr )
267 {
268 const FunctionDecl* canonicalFunctionDecl = calleeFunctionDecl->getCanonicalDecl();
269 if (!isCalleeFunctionInteresting(canonicalFunctionDecl)) {
270 return;
271 }
272
273 calledFromSet.emplace(toString(compat::getBeginLoc(expr)), niceName(canonicalFunctionDecl));
274
275 if (const UnaryOperator* unaryOp = dyn_cast_or_null<UnaryOperator>(getParentStmt(expr))) {
276 if (unaryOp->getOpcode() == UO_AddrOf) {
277 addressOfSet.insert(niceName(canonicalFunctionDecl));
278 }
279 }
280
281 const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
282 if (maTraversingFunctions.empty())
283 {
284 calledFromOutsideSet.insert(niceName(canonicalFunctionDecl));
285 }
286 else
287 {
288 const CXXMethodDecl* callsiteParentMethodDecl = dyn_cast<CXXMethodDecl>(maTraversingFunctions.back());
289 if (!callsiteParentMethodDecl
290 || calleeMethodDecl->getParent() != callsiteParentMethodDecl->getParent())
291 {
292 calledFromOutsideSet.insert(niceName(canonicalFunctionDecl));
293 }
294 }
295 }
296
isCalleeFunctionInteresting(const FunctionDecl * functionDecl)297 bool ExpandableMethods::isCalleeFunctionInteresting(const FunctionDecl* functionDecl)
298 {
299 // ignore stuff that forms part of the stable URE interface
300 if (isInUnoIncludeFile(functionDecl)) {
301 return false;
302 }
303 if (isa<CXXDestructorDecl>(functionDecl)) {
304 return false;
305 }
306 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
307 return false;
308 }
309 if (isa<CXXConstructorDecl>(functionDecl)
310 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
311 {
312 return false;
313 }
314 if (!functionDecl->getLocation().isValid() || ignoreLocation(functionDecl)) {
315 return false;
316 }
317 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
318 if (!methodDecl || methodDecl->isVirtual()) {
319 return false;
320 }
321 return true;
322 }
323
324 loplugin::Plugin::Registration< ExpandableMethods > X("expandablemethods", false);
325
326 }
327
328 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
329