Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / expandablemethods.cxx
blob20d5a6998e399b801915c11ea2ceee30a715e9da
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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 */
10 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <fstream>
14 #include <set>
15 #include <unordered_map>
18 #include "clang/AST/Attr.h"
20 #include "plugin.hxx"
21 #include "compat.hxx"
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.
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
33 namespace {
35 struct MyFuncInfo
37 std::string access;
38 std::string returnType;
39 std::string nameAndParams;
40 std::string sourceLocation;
43 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
45 return std::tie(lhs.returnType, lhs.nameAndParams)
46 < std::tie(rhs.returnType, rhs.nameAndParams);
49 // try to limit the voluminous output a little
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;
58 class ExpandableMethods:
59 public RecursiveASTVisitor<ExpandableMethods>, public loplugin::Plugin
61 public:
62 explicit ExpandableMethods(loplugin::InstantiationData const & data):
63 Plugin(data) {}
65 virtual void run() override
67 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
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
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();
90 bool shouldVisitTemplateInstantiations () const { return true; }
91 bool shouldVisitImplicitCode() const { return true; }
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* );
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* );
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;
113 MyFuncInfo ExpandableMethods::niceName(const FunctionDecl* functionDecl)
115 if (functionDecl->getInstantiatedFromMemberFunction())
116 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
117 else if (functionDecl->getClassScopeSpecializationPattern())
118 functionDecl = functionDecl->getClassScopeSpecializationPattern();
119 else if (functionDecl->getTemplateInstantiationPattern())
120 functionDecl = functionDecl->getTemplateInstantiationPattern();
122 MyFuncInfo aInfo;
123 switch (functionDecl->getAccess())
125 case AS_public: aInfo.access = "public"; break;
126 case AS_private: aInfo.access = "private"; break;
127 case AS_protected: aInfo.access = "protected"; break;
128 default: aInfo.access = "unknown"; break;
130 if (!isa<CXXConstructorDecl>(functionDecl)) {
131 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
132 } else {
133 aInfo.returnType = "";
136 if (isa<CXXMethodDecl>(functionDecl)) {
137 const CXXRecordDecl* recordDecl = dyn_cast<CXXMethodDecl>(functionDecl)->getParent();
138 aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
139 aInfo.nameAndParams += "::";
141 aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
142 bool bFirst = true;
143 for (const ParmVarDecl *pParmVarDecl : compat::parameters(*functionDecl)) {
144 if (bFirst)
145 bFirst = false;
146 else
147 aInfo.nameAndParams += ",";
148 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
150 aInfo.nameAndParams += ")";
151 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
152 aInfo.nameAndParams += " const";
155 aInfo.sourceLocation = toString( functionDecl->getLocation() );
157 return aInfo;
160 std::string ExpandableMethods::toString(SourceLocation loc)
162 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
163 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
164 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
165 loplugin::normalizeDotDotInFilePath(sourceLocation);
166 return sourceLocation;
169 bool ExpandableMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
171 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
172 if (!isCalleeFunctionInteresting(canonicalFunctionDecl)) {
173 return true;
175 definitionSet.insert(niceName(canonicalFunctionDecl));
177 if (functionDecl->doesThisDeclarationHaveABody()) {
178 bool bLargeFunction = false;
179 if (const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(functionDecl->getBody())) {
180 if (compoundStmt->size() > 1) {
181 bLargeFunction = true;
183 if (!bLargeFunction) {
184 auto s1 = compiler.getSourceManager().getCharacterData(compoundStmt->getLBracLoc());
185 auto s2 = compiler.getSourceManager().getCharacterData(compoundStmt->getRBracLoc());
186 bLargeFunction = (s2 - s1) > 40;
187 // any function that uses a parameter more than once
188 if (!bLargeFunction) {
189 StringRef bodyText(s1, s2-s1);
190 for (const ParmVarDecl* param : compat::parameters(*functionDecl)) {
191 StringRef name = param->getName();
192 if (name.empty())
193 continue;
194 size_t idx = bodyText.find(name);
195 if (idx != StringRef::npos && bodyText.find(name, idx+1) != StringRef::npos) {
196 bLargeFunction = true;
197 break;
203 if (bLargeFunction) {
204 largeFunctionSet.insert(niceName(canonicalFunctionDecl));
207 return true;
210 bool ExpandableMethods::TraverseFunctionDecl( FunctionDecl* p )
212 maTraversingFunctions.push_back(p);
213 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(p);
214 maTraversingFunctions.pop_back();
215 return ret;
217 bool ExpandableMethods::TraverseCXXMethodDecl( CXXMethodDecl* p )
219 maTraversingFunctions.push_back(p);
220 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(p);
221 maTraversingFunctions.pop_back();
222 return ret;
224 bool ExpandableMethods::TraverseCXXConstructorDecl( CXXConstructorDecl* p )
226 maTraversingFunctions.push_back(p);
227 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(p);
228 maTraversingFunctions.pop_back();
229 return ret;
231 bool ExpandableMethods::TraverseCXXConversionDecl( CXXConversionDecl* p )
233 maTraversingFunctions.push_back(p);
234 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(p);
235 maTraversingFunctions.pop_back();
236 return ret;
238 bool ExpandableMethods::TraverseCXXDestructorDecl( CXXDestructorDecl* p )
240 maTraversingFunctions.push_back(p);
241 bool ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(p);
242 maTraversingFunctions.pop_back();
243 return ret;
246 bool ExpandableMethods::VisitMemberExpr( const MemberExpr* memberExpr )
248 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(memberExpr->getMemberDecl());
249 if (functionDecl) {
250 functionTouchedFromExpr(functionDecl, memberExpr);
252 return true;
255 bool ExpandableMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
257 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
258 if (functionDecl) {
259 functionTouchedFromExpr(functionDecl, declRefExpr);
261 return true;
264 void ExpandableMethods::functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr )
266 const FunctionDecl* canonicalFunctionDecl = calleeFunctionDecl->getCanonicalDecl();
267 if (!isCalleeFunctionInteresting(canonicalFunctionDecl)) {
268 return;
271 calledFromSet.emplace(toString(expr->getLocStart()), niceName(canonicalFunctionDecl));
273 if (const UnaryOperator* unaryOp = dyn_cast_or_null<UnaryOperator>(getParentStmt(expr))) {
274 if (unaryOp->getOpcode() == UO_AddrOf) {
275 addressOfSet.insert(niceName(canonicalFunctionDecl));
279 const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
280 if (maTraversingFunctions.empty())
282 calledFromOutsideSet.insert(niceName(canonicalFunctionDecl));
284 else
286 const CXXMethodDecl* callsiteParentMethodDecl = dyn_cast<CXXMethodDecl>(maTraversingFunctions.back());
287 if (!callsiteParentMethodDecl
288 || calleeMethodDecl->getParent() != callsiteParentMethodDecl->getParent())
290 calledFromOutsideSet.insert(niceName(canonicalFunctionDecl));
295 bool ExpandableMethods::isCalleeFunctionInteresting(const FunctionDecl* functionDecl)
297 // ignore stuff that forms part of the stable URE interface
298 if (isInUnoIncludeFile(functionDecl)) {
299 return false;
301 if (isa<CXXDestructorDecl>(functionDecl)) {
302 return false;
304 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
305 return false;
307 if (isa<CXXConstructorDecl>(functionDecl)
308 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
310 return false;
312 if (!functionDecl->getLocation().isValid() || ignoreLocation(functionDecl)) {
313 return false;
315 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
316 if (!methodDecl || methodDecl->isVirtual()) {
317 return false;
319 return true;
322 loplugin::Plugin::Registration< ExpandableMethods > X("expandablemethods", false);
326 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */