Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / unusedmethods.cxx
bloba84b7e8d237d4b82c5c8db82f919359d849edc26
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 This plugin performs 3 different analyses:
26 (1) Find unused methods
27 (2) Find methods whose return types are never evaluated
28 (3) Find methods which are public, but are never called from outside the class i.e. they can be private
30 It does so, by dumping various call/definition/use info to a log file.
31 Then we will post-process the various lists and find the set of unused methods.
33 Be warned that it produces around 15G of log file.
35 The process goes something like this:
36 $ make check
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedmethods' check
38 $ ./compilerplugins/clang/unusedmethods.py
40 and then
41 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedmethodsremove' $dir; done
42 to auto-remove the method declarations
44 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
45 to get it to work :-)
49 namespace {
51 struct MyFuncInfo
53 std::string access;
54 std::string returnType;
55 std::string nameAndParams;
56 std::string sourceLocation;
57 std::string virtualness;
60 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
62 return std::tie(lhs.returnType, lhs.nameAndParams)
63 < std::tie(rhs.returnType, rhs.nameAndParams);
66 // try to limit the voluminous output a little
68 // for the "unused method" analysis
69 static std::set<MyFuncInfo> callSet;
70 static std::set<MyFuncInfo> definitionSet;
71 // for the "unused return type" analysis
72 static std::set<MyFuncInfo> usedReturnSet;
73 // for the "can be private" analysis
74 static std::set<MyFuncInfo> calledFromOutsideSet;
77 class UnusedMethods:
78 public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin
80 public:
81 explicit UnusedMethods(loplugin::InstantiationData const & data):
82 Plugin(data) {}
84 virtual void run() override
86 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
88 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
89 // writing to the same logfile
91 std::string output;
92 for (const MyFuncInfo & s : definitionSet)
93 output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams
94 + "\t" + s.sourceLocation + "\t" + s.virtualness + "\n";
95 // for the "unused method" analysis
96 for (const MyFuncInfo & s : callSet)
97 output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
98 // for the "unused return type" analysis
99 for (const MyFuncInfo & s : usedReturnSet)
100 output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
101 // for the "method can be private" analysis
102 for (const MyFuncInfo & s : calledFromOutsideSet)
103 output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
104 std::ofstream myfile;
105 myfile.open( WORKDIR "/loplugin.unusedmethods.log", std::ios::app | std::ios::out);
106 myfile << output;
107 myfile.close();
110 bool shouldVisitTemplateInstantiations () const { return true; }
111 bool shouldVisitImplicitCode() const { return true; }
113 bool VisitCallExpr(CallExpr* );
114 bool VisitFunctionDecl( const FunctionDecl* decl );
115 bool VisitDeclRefExpr( const DeclRefExpr* );
116 bool VisitCXXConstructExpr( const CXXConstructExpr* );
117 bool TraverseCXXRecordDecl( CXXRecordDecl* );
118 bool TraverseFunctionDecl( FunctionDecl* );
119 bool TraverseCXXMethodDecl( CXXMethodDecl* );
120 bool TraverseCXXConversionDecl( CXXConversionDecl* );
121 #if CLANG_VERSION >= 50000
122 bool TraverseCXXDeductionGuideDecl( CXXDeductionGuideDecl* );
123 #endif
124 private:
125 void logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet);
126 MyFuncInfo niceName(const FunctionDecl* functionDecl);
127 std::string toString(SourceLocation loc);
128 void functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr );
129 CXXRecordDecl const * currentCxxRecordDecl = nullptr;
130 FunctionDecl const * currentFunctionDecl = nullptr;
133 MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
135 if (functionDecl->getInstantiatedFromMemberFunction())
136 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
137 else if (functionDecl->getClassScopeSpecializationPattern())
138 functionDecl = functionDecl->getClassScopeSpecializationPattern();
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 (const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
157 const CXXRecordDecl* recordDecl = methodDecl->getParent();
158 aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
159 aInfo.nameAndParams += "::";
160 if (methodDecl->isVirtual())
161 aInfo.virtualness = "virtual";
163 aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
164 bool bFirst = true;
165 for (const ParmVarDecl *pParmVarDecl : compat::parameters(*functionDecl)) {
166 if (bFirst)
167 bFirst = false;
168 else
169 aInfo.nameAndParams += ",";
170 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
172 aInfo.nameAndParams += ")";
173 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
174 aInfo.nameAndParams += " const";
177 aInfo.sourceLocation = toString( functionDecl->getLocation() );
179 return aInfo;
182 std::string UnusedMethods::toString(SourceLocation loc)
184 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
185 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
186 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
187 loplugin::normalizeDotDotInFilePath(sourceLocation);
188 return sourceLocation;
191 // For virtual/overriding methods, we need to pretend we called the root method(s),
192 // so that they get marked as used.
193 void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet)
195 functionDecl = functionDecl->getCanonicalDecl();
196 bool bCalledSuperMethod = false;
197 if (isa<CXXMethodDecl>(functionDecl)) {
198 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
199 for(CXXMethodDecl::method_iterator it = methodDecl->begin_overridden_methods();
200 it != methodDecl->end_overridden_methods(); ++it)
202 logCallToRootMethods(*it, funcSet);
203 bCalledSuperMethod = true;
206 if (!bCalledSuperMethod)
208 while (functionDecl->getTemplateInstantiationPattern())
209 functionDecl = functionDecl->getTemplateInstantiationPattern();
210 if (functionDecl->getLocation().isValid() && !ignoreLocation( functionDecl )
211 && !functionDecl->isExternC())
212 funcSet.insert(niceName(functionDecl));
216 bool UnusedMethods::VisitCallExpr(CallExpr* expr)
218 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
219 // from template instantiation deep inside the STL and other external code
221 FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
222 if (calleeFunctionDecl == nullptr) {
223 Expr* callee = expr->getCallee()->IgnoreParenImpCasts();
224 DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
225 if (dr) {
226 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
227 if (calleeFunctionDecl)
228 goto gotfunc;
230 return true;
233 gotfunc:
236 if (currentFunctionDecl == calleeFunctionDecl)
237 ; // for "unused method" analysis, ignore recursive calls
238 else if (currentFunctionDecl
239 && currentFunctionDecl->getIdentifier()
240 && currentFunctionDecl->getName() == "Clone"
241 && currentFunctionDecl->getParent() == calleeFunctionDecl->getParent()
242 && isa<CXXConstructorDecl>(calleeFunctionDecl))
243 ; // if we are inside Clone(), ignore calls to the same class's constructor
244 else
245 logCallToRootMethods(calleeFunctionDecl, callSet);
247 const Stmt* parent = getParentStmt(expr);
249 // Now do the checks necessary for the "can be private" analysis
250 CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
251 if (calleeMethodDecl && calleeMethodDecl->getAccess() != AS_private)
253 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(expr);
254 if (parentFunctionOfCallSite != calleeFunctionDecl) {
255 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite)) {
256 calledFromOutsideSet.insert(niceName(calleeFunctionDecl));
261 // Now do the checks necessary for the "unused return value" analysis
262 if (calleeFunctionDecl->getReturnType()->isVoidType()) {
263 return true;
265 if (!parent) {
266 // we will get null parent if it's under a CXXConstructExpr node
267 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
268 return true;
270 if (isa<Expr>(parent) || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
271 || isa<IfStmt>(parent) || isa<SwitchStmt>(parent) || isa<ForStmt>(parent)
272 || isa<WhileStmt>(parent) || isa<DoStmt>(parent)
273 || isa<CXXForRangeStmt>(parent))
275 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
276 return true;
278 if (isa<CompoundStmt>(parent) || isa<DefaultStmt>(parent) || isa<CaseStmt>(parent)
279 || isa<LabelStmt>(parent))
281 return true;
283 parent->dump();
284 return true;
287 bool UnusedMethods::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
289 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
290 // from template instantiation deep inside the STL and other external code
292 const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor();
293 constructorDecl = constructorDecl->getCanonicalDecl();
295 if (!constructorDecl->getLocation().isValid() || ignoreLocation(constructorDecl)) {
296 return true;
299 logCallToRootMethods(constructorDecl, callSet);
301 // Now do the checks necessary for the "can be private" analysis
302 if (constructorDecl->getParent() != currentCxxRecordDecl)
303 calledFromOutsideSet.insert(niceName(constructorDecl));
305 return true;
308 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
310 // ignore stuff that forms part of the stable URE interface
311 if (isInUnoIncludeFile(functionDecl)) {
312 return true;
314 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
315 if (isa<CXXDestructorDecl>(functionDecl)) {
316 return true;
318 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
319 return true;
321 if (isa<CXXConstructorDecl>(functionDecl)
322 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
324 return true;
326 if (!canonicalFunctionDecl->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl)) {
327 return true;
329 // ignore method overrides, since the call will show up as being directed to the root method
330 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
331 if (methodDecl && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>())) {
332 return true;
334 if (!functionDecl->isExternC()) {
335 MyFuncInfo funcInfo = niceName(canonicalFunctionDecl);
336 definitionSet.insert(funcInfo);
338 return true;
341 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
343 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
344 if (!functionDecl) {
345 return true;
347 logCallToRootMethods(functionDecl->getCanonicalDecl(), callSet);
348 logCallToRootMethods(functionDecl->getCanonicalDecl(), usedReturnSet);
350 // Now do the checks necessary for the "can be private" analysis
351 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
352 if (methodDecl && methodDecl->getAccess() != AS_private)
354 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(declRefExpr);
355 if (parentFunctionOfCallSite != functionDecl) {
356 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite)) {
357 calledFromOutsideSet.insert(niceName(functionDecl));
362 return true;
365 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
367 auto copy = currentCxxRecordDecl;
368 currentCxxRecordDecl = cxxRecordDecl;
369 bool ret = RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
370 currentCxxRecordDecl = copy;
371 return ret;
374 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl* f)
376 auto copy = currentFunctionDecl;
377 currentFunctionDecl = f;
378 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f);
379 currentFunctionDecl = copy;
380 return ret;
382 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl* f)
384 auto copy = currentFunctionDecl;
385 currentFunctionDecl = f;
386 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f);
387 currentFunctionDecl = copy;
388 return ret;
390 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl* f)
392 auto copy = currentFunctionDecl;
393 currentFunctionDecl = f;
394 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f);
395 currentFunctionDecl = copy;
396 return ret;
398 #if CLANG_VERSION >= 50000
399 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f)
401 auto copy = currentFunctionDecl;
402 currentFunctionDecl = f;
403 bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f);
404 currentFunctionDecl = copy;
405 return ret;
407 #endif
409 loplugin::Plugin::Registration< UnusedMethods > X("unusedmethods", false);
413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */