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