bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / unusedmethods.cxx
blobe24fec99c3d2bbf18ec296a05806db5c06088ef5
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"
22 /**
23 This plugin performs 3 different analyses:
25 (1) Find unused methods
26 (2) Find methods whose return types are never evaluated
27 (3) Find methods which are public, but are never called from outside the class i.e. they can be private
29 It does so, by dumping various call/definition/use info to a log file.
30 Then we will post-process the various lists and find the set of unused methods.
32 Be warned that it produces around 15G of log file.
34 The process goes something like this:
35 $ make check
36 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedmethods' check
37 $ ./compilerplugins/clang/unusedmethods.py
39 and then
40 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedmethodsremove' $dir; done
41 to auto-remove the method declarations
43 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
44 to get it to work :-)
48 namespace {
50 struct MyFuncInfo
52 std::string access;
53 std::string returnType;
54 std::string nameAndParams;
55 std::string sourceLocation;
56 std::string virtualness;
59 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
61 return std::tie(lhs.returnType, lhs.nameAndParams)
62 < std::tie(rhs.returnType, rhs.nameAndParams);
65 // try to limit the voluminous output a little
67 // for the "unused method" analysis
68 static std::set<MyFuncInfo> callSet;
69 static std::set<MyFuncInfo> definitionSet;
70 // for the "unused return type" analysis
71 static std::set<MyFuncInfo> usedReturnSet;
72 // for the "can be private" analysis
73 static std::set<MyFuncInfo> calledFromOutsideSet;
76 class UnusedMethods:
77 public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin
79 public:
80 explicit UnusedMethods(loplugin::InstantiationData const & data):
81 Plugin(data) {}
83 virtual void run() override
85 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
87 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
88 // writing to the same logfile
90 std::string output;
91 for (const MyFuncInfo & s : definitionSet)
92 output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams
93 + "\t" + s.sourceLocation + "\t" + s.virtualness + "\n";
94 // for the "unused method" analysis
95 for (const MyFuncInfo & s : callSet)
96 output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
97 // for the "unused return type" analysis
98 for (const MyFuncInfo & s : usedReturnSet)
99 output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
100 // for the "method can be private" analysis
101 for (const MyFuncInfo & s : calledFromOutsideSet)
102 output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
103 std::ofstream myfile;
104 myfile.open( WORKDIR "/loplugin.unusedmethods.log", std::ios::app | std::ios::out);
105 myfile << output;
106 myfile.close();
109 bool shouldVisitTemplateInstantiations () const { return true; }
110 bool shouldVisitImplicitCode() const { return true; }
112 bool VisitCallExpr(CallExpr* );
113 bool VisitFunctionDecl( const FunctionDecl* decl );
114 bool VisitDeclRefExpr( const DeclRefExpr* );
115 bool VisitCXXConstructExpr( const CXXConstructExpr* );
116 bool TraverseCXXRecordDecl( CXXRecordDecl* );
117 bool TraverseFunctionDecl( FunctionDecl* );
118 bool TraverseCXXMethodDecl( CXXMethodDecl* );
119 bool TraverseCXXConversionDecl( CXXConversionDecl* );
120 bool TraverseCXXDeductionGuideDecl( CXXDeductionGuideDecl* );
122 private:
123 void logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet);
124 MyFuncInfo niceName(const FunctionDecl* functionDecl);
125 std::string toString(SourceLocation loc);
126 void functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr );
127 CXXRecordDecl const * currentCxxRecordDecl = nullptr;
128 FunctionDecl const * currentFunctionDecl = nullptr;
131 MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
133 if (functionDecl->getInstantiatedFromMemberFunction())
134 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
135 #if CLANG_VERSION < 90000
136 else if (functionDecl->getClassScopeSpecializationPattern())
137 functionDecl = functionDecl->getClassScopeSpecializationPattern();
138 #endif
139 else if (functionDecl->getTemplateInstantiationPattern())
140 functionDecl = functionDecl->getTemplateInstantiationPattern();
142 MyFuncInfo aInfo;
143 switch (functionDecl->getAccess())
145 case AS_public: aInfo.access = "public"; break;
146 case AS_private: aInfo.access = "private"; break;
147 case AS_protected: aInfo.access = "protected"; break;
148 default: aInfo.access = "unknown"; break;
150 if (!isa<CXXConstructorDecl>(functionDecl)) {
151 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
152 } else {
153 aInfo.returnType = "";
156 if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
157 const CXXRecordDecl* recordDecl = methodDecl->getParent();
158 aInfo.nameAndParams = recordDecl->getQualifiedNameAsString()
159 + "::"
160 + functionDecl->getNameAsString()
161 + "(";
162 if (methodDecl->isVirtual())
163 aInfo.virtualness = "virtual";
165 else
167 aInfo.nameAndParams = functionDecl->getQualifiedNameAsString() + "(";
169 bool bFirst = true;
170 for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
171 if (bFirst)
172 bFirst = false;
173 else
174 aInfo.nameAndParams += ",";
175 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
177 aInfo.nameAndParams += ")";
178 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
179 aInfo.nameAndParams += " const";
182 aInfo.sourceLocation = toString( functionDecl->getLocation() );
184 return aInfo;
187 std::string UnusedMethods::toString(SourceLocation loc)
189 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
190 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
191 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
192 loplugin::normalizeDotDotInFilePath(sourceLocation);
193 return sourceLocation;
196 // For virtual/overriding methods, we need to pretend we called the root method(s),
197 // so that they get marked as used.
198 void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet)
200 functionDecl = functionDecl->getCanonicalDecl();
201 bool bCalledSuperMethod = false;
202 if (isa<CXXMethodDecl>(functionDecl)) {
203 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
204 for(CXXMethodDecl::method_iterator it = methodDecl->begin_overridden_methods();
205 it != methodDecl->end_overridden_methods(); ++it)
207 logCallToRootMethods(*it, funcSet);
208 bCalledSuperMethod = true;
211 if (!bCalledSuperMethod)
213 while (functionDecl->getTemplateInstantiationPattern())
214 functionDecl = functionDecl->getTemplateInstantiationPattern();
215 if (functionDecl->getLocation().isValid() && !ignoreLocation( functionDecl )
216 && !functionDecl->isExternC())
217 funcSet.insert(niceName(functionDecl));
221 bool UnusedMethods::VisitCallExpr(CallExpr* expr)
223 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
224 // from template instantiation deep inside the STL and other external code
226 FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
227 if (calleeFunctionDecl == nullptr) {
228 Expr* callee = expr->getCallee()->IgnoreParenImpCasts();
229 DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
230 if (dr) {
231 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
232 if (calleeFunctionDecl)
233 goto gotfunc;
235 return true;
238 gotfunc:
241 if (currentFunctionDecl == calleeFunctionDecl)
242 ; // for "unused method" analysis, ignore recursive calls
243 else if (currentFunctionDecl
244 && currentFunctionDecl->getIdentifier()
245 && currentFunctionDecl->getName() == "Clone"
246 && currentFunctionDecl->getParent() == calleeFunctionDecl->getParent()
247 && isa<CXXConstructorDecl>(calleeFunctionDecl))
248 ; // if we are inside Clone(), ignore calls to the same class's constructor
249 else
250 logCallToRootMethods(calleeFunctionDecl, callSet);
252 const Stmt* parent = getParentStmt(expr);
254 // Now do the checks necessary for the "can be private" analysis
255 CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
256 if (calleeMethodDecl && calleeMethodDecl->getAccess() != AS_private)
258 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(expr);
259 if (parentFunctionOfCallSite != calleeFunctionDecl) {
260 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite)) {
261 calledFromOutsideSet.insert(niceName(calleeFunctionDecl));
266 // Now do the checks necessary for the "unused return value" analysis
267 if (calleeFunctionDecl->getReturnType()->isVoidType()) {
268 return true;
270 if (!parent) {
271 // we will get null parent if it's under a CXXConstructExpr node
272 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
273 return true;
275 if (isa<Expr>(parent) || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
276 || isa<IfStmt>(parent) || isa<SwitchStmt>(parent) || isa<ForStmt>(parent)
277 || isa<WhileStmt>(parent) || isa<DoStmt>(parent)
278 || isa<CXXForRangeStmt>(parent))
280 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
281 return true;
283 if (isa<CompoundStmt>(parent) || isa<DefaultStmt>(parent) || isa<CaseStmt>(parent)
284 || isa<LabelStmt>(parent))
286 return true;
288 parent->dump();
289 return true;
292 bool UnusedMethods::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
294 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
295 // from template instantiation deep inside the STL and other external code
297 const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor();
298 constructorDecl = constructorDecl->getCanonicalDecl();
300 if (!constructorDecl->getLocation().isValid() || ignoreLocation(constructorDecl)) {
301 return true;
304 logCallToRootMethods(constructorDecl, callSet);
306 // Now do the checks necessary for the "can be private" analysis
307 if (constructorDecl->getParent() != currentCxxRecordDecl)
308 calledFromOutsideSet.insert(niceName(constructorDecl));
310 return true;
313 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
315 // ignore stuff that forms part of the stable URE interface
316 if (isInUnoIncludeFile(functionDecl)) {
317 return true;
319 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
320 if (isa<CXXDestructorDecl>(functionDecl)) {
321 return true;
323 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
324 return true;
326 if (isa<CXXConstructorDecl>(functionDecl)
327 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
329 return true;
331 if (!canonicalFunctionDecl->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl)) {
332 return true;
334 // ignore method overrides, since the call will show up as being directed to the root method
335 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
336 if (methodDecl && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>())) {
337 return true;
339 if (!functionDecl->isExternC()) {
340 MyFuncInfo funcInfo = niceName(canonicalFunctionDecl);
341 definitionSet.insert(funcInfo);
343 return true;
346 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
348 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
349 if (!functionDecl) {
350 return true;
352 logCallToRootMethods(functionDecl->getCanonicalDecl(), callSet);
353 logCallToRootMethods(functionDecl->getCanonicalDecl(), usedReturnSet);
355 // Now do the checks necessary for the "can be private" analysis
356 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
357 if (methodDecl && methodDecl->getAccess() != AS_private)
359 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(declRefExpr);
360 if (parentFunctionOfCallSite != functionDecl) {
361 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite)) {
362 calledFromOutsideSet.insert(niceName(functionDecl));
367 return true;
370 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
372 auto copy = currentCxxRecordDecl;
373 currentCxxRecordDecl = cxxRecordDecl;
374 bool ret = RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
375 currentCxxRecordDecl = copy;
376 return ret;
379 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl* f)
381 auto copy = currentFunctionDecl;
382 currentFunctionDecl = f;
383 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f);
384 currentFunctionDecl = copy;
385 return ret;
387 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl* f)
389 auto copy = currentFunctionDecl;
390 currentFunctionDecl = f;
391 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f);
392 currentFunctionDecl = copy;
393 return ret;
395 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl* f)
397 auto copy = currentFunctionDecl;
398 currentFunctionDecl = f;
399 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f);
400 currentFunctionDecl = copy;
401 return ret;
403 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f)
405 auto copy = currentFunctionDecl;
406 currentFunctionDecl = f;
407 bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f);
408 currentFunctionDecl = copy;
409 return ret;
412 loplugin::Plugin::Registration< UnusedMethods > X("unusedmethods", false);
416 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */