lok: getSlideShowInfo: interactions: check that properties are available
[LibreOffice.git] / compilerplugins / clang / unusedmethods.cxx
blob5b10eae7e9df176d6aa5b588403ced12168d8949
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 "config_clang.h"
22 #include "plugin.hxx"
24 /**
25 This plugin performs 3 different analyses:
27 (1) Find unused methods
28 (2) Find methods whose return types are never evaluated
29 (3) Find methods which are public, but are never called from outside the class i.e. they can be private
31 It does so, by dumping various call/definition/use info to a log file.
32 Then we will post-process the various lists and find the set of unused methods.
34 Be warned that it produces around 15G of log file.
36 The process goes something like this:
37 $ make check
38 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedmethods' check
39 $ ./compilerplugins/clang/unusedmethods.py
41 and then
42 $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedmethodsremove' $dir; done
43 to auto-remove the method declarations
45 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
46 to get it to work :-)
50 namespace {
52 struct MyFuncInfo
54 std::string access;
55 std::string returnType;
56 std::string nameAndParams;
57 std::string sourceLocation;
58 std::string virtualness;
61 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
63 return std::tie(lhs.returnType, lhs.nameAndParams)
64 < std::tie(rhs.returnType, rhs.nameAndParams);
67 // try to limit the voluminous output a little
69 // for the "unused method" analysis
70 static std::set<MyFuncInfo> callSet;
71 static std::set<MyFuncInfo> definitionSet;
72 // for the "unused return type" analysis
73 static std::set<MyFuncInfo> usedReturnSet;
74 // for the "can be private" analysis
75 static std::set<MyFuncInfo> calledFromOutsideSet;
78 class UnusedMethods:
79 public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin
81 public:
82 explicit UnusedMethods(loplugin::InstantiationData const & data):
83 Plugin(data) {}
85 virtual void run() override
87 handler.enableTreeWideAnalysisMode();
89 StringRef fn(handler.getMainFileName());
90 // ignore external code, makes this run faster
91 if (fn.contains("UnpackedTarball"))
92 return;
94 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
96 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
97 // writing to the same logfile
99 std::string output;
100 for (const MyFuncInfo & s : definitionSet)
102 output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams
103 + "\t" + s.sourceLocation + "\t" + s.virtualness + "\n";
105 // for the "unused method" analysis
106 for (const MyFuncInfo & s : callSet)
107 output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
108 // for the "unused return type" analysis
109 for (const MyFuncInfo & s : usedReturnSet)
110 output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
111 // for the "method can be private" analysis
112 for (const MyFuncInfo & s : calledFromOutsideSet)
113 output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
114 std::ofstream myfile;
115 myfile.open( WORKDIR "/loplugin.unusedmethods.log", std::ios::app | std::ios::out);
116 myfile << output;
117 myfile.close();
120 bool shouldVisitTemplateInstantiations () const { return true; }
121 bool shouldVisitImplicitCode() const { return true; }
123 bool VisitCallExpr(CallExpr* );
124 bool VisitFunctionDecl( const FunctionDecl* decl );
125 bool VisitDeclRefExpr( const DeclRefExpr* );
126 bool VisitCXXConstructExpr( const CXXConstructExpr* );
127 bool TraverseCXXRecordDecl( CXXRecordDecl* );
128 bool TraverseFunctionDecl( FunctionDecl* );
129 bool TraverseCXXMethodDecl( CXXMethodDecl* );
130 bool TraverseCXXConversionDecl( CXXConversionDecl* );
131 bool TraverseCXXDeductionGuideDecl( CXXDeductionGuideDecl* );
133 private:
134 void logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet);
135 MyFuncInfo niceName(const FunctionDecl* functionDecl);
136 std::string toString(SourceLocation loc);
137 void functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr );
139 CXXRecordDecl const * currentCxxRecordDecl = nullptr;
140 FunctionDecl const * currentFunctionDecl = nullptr;
143 MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
145 for(;;)
147 if (functionDecl->getInstantiatedFromMemberFunction())
148 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
149 else if (functionDecl->getTemplateInstantiationPattern())
150 functionDecl = functionDecl->getTemplateInstantiationPattern();
151 else
152 break;
155 MyFuncInfo aInfo;
156 switch (functionDecl->getAccess())
158 case AS_public: aInfo.access = "public"; break;
159 case AS_private: aInfo.access = "private"; break;
160 case AS_protected: aInfo.access = "protected"; break;
161 default: aInfo.access = "unknown"; break;
163 if (!isa<CXXConstructorDecl>(functionDecl)) {
164 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
165 } else {
166 aInfo.returnType = "";
169 if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
170 const CXXRecordDecl* recordDecl = methodDecl->getParent();
171 aInfo.nameAndParams = recordDecl->getQualifiedNameAsString()
172 + "::"
173 + functionDecl->getNameAsString()
174 + "(";
175 if (methodDecl->isVirtual())
176 aInfo.virtualness = "virtual";
178 else
180 aInfo.nameAndParams = functionDecl->getQualifiedNameAsString() + "(";
182 bool bFirst = true;
183 for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
184 if (bFirst)
185 bFirst = false;
186 else
187 aInfo.nameAndParams += ",";
188 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
190 aInfo.nameAndParams += ")";
191 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
192 aInfo.nameAndParams += " const";
195 aInfo.sourceLocation = toString( functionDecl->getLocation() );
197 return aInfo;
200 std::string UnusedMethods::toString(SourceLocation loc)
202 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
203 StringRef name = getFilenameOfLocation(expansionLoc);
204 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
205 loplugin::normalizeDotDotInFilePath(sourceLocation);
206 return sourceLocation;
209 // For virtual/overriding methods, we need to pretend we called the root method(s),
210 // so that they get marked as used.
211 void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet)
213 functionDecl = functionDecl->getCanonicalDecl();
214 bool bCalledSuperMethod = false;
215 if (isa<CXXMethodDecl>(functionDecl)) {
216 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
217 for(CXXMethodDecl::method_iterator it = methodDecl->begin_overridden_methods();
218 it != methodDecl->end_overridden_methods(); ++it)
220 logCallToRootMethods(*it, funcSet);
221 bCalledSuperMethod = true;
224 if (!bCalledSuperMethod)
226 while (functionDecl->getTemplateInstantiationPattern())
227 functionDecl = functionDecl->getTemplateInstantiationPattern();
228 if (functionDecl->getLocation().isValid() && !ignoreLocation( functionDecl->getBeginLoc() )
229 && !functionDecl->isExternC())
230 funcSet.insert(niceName(functionDecl));
234 bool UnusedMethods::VisitCallExpr(CallExpr* expr)
236 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
237 // from template instantiation deep inside the STL and other external code
239 FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
240 if (calleeFunctionDecl == nullptr) {
241 Expr* callee = expr->getCallee()->IgnoreParenImpCasts();
242 DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
243 if (dr) {
244 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
245 if (calleeFunctionDecl)
246 goto gotfunc;
248 return true;
251 gotfunc:
254 if (currentFunctionDecl == calleeFunctionDecl)
255 ; // for "unused method" analysis, ignore recursive calls
256 else if (currentFunctionDecl
257 && currentFunctionDecl->getIdentifier()
258 && currentFunctionDecl->getName() == "Clone"
259 && currentFunctionDecl->getParent() == calleeFunctionDecl->getParent()
260 && isa<CXXConstructorDecl>(calleeFunctionDecl))
261 ; // if we are inside Clone(), ignore calls to the same class's constructor
262 else
263 logCallToRootMethods(calleeFunctionDecl, callSet);
265 const Stmt* parent = getParentStmt(expr);
267 // Now do the checks necessary for the "can be private" analysis
268 CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
269 if (calleeMethodDecl && calleeMethodDecl->getAccess() != AS_private)
271 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(expr);
272 if (parentFunctionOfCallSite != calleeFunctionDecl) {
273 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite->getBeginLoc())) {
274 calledFromOutsideSet.insert(niceName(calleeFunctionDecl));
279 // Now do the checks necessary for the "unused return value" analysis
280 if (calleeFunctionDecl->getReturnType()->isVoidType()) {
281 return true;
283 if (!parent) {
284 // we will get null parent if it's under a CXXConstructExpr node
285 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
286 return true;
288 if (isa<Expr>(parent) || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
289 || isa<IfStmt>(parent) || isa<SwitchStmt>(parent) || isa<ForStmt>(parent)
290 || isa<WhileStmt>(parent) || isa<DoStmt>(parent)
291 || isa<CXXForRangeStmt>(parent))
293 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
294 return true;
296 if (isa<CompoundStmt>(parent) || isa<DefaultStmt>(parent) || isa<CaseStmt>(parent)
297 || isa<LabelStmt>(parent))
299 return true;
301 parent->dump();
302 return true;
305 bool UnusedMethods::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
307 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
308 // from template instantiation deep inside the STL and other external code
310 const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor();
311 constructorDecl = constructorDecl->getCanonicalDecl();
313 if (!constructorDecl->getLocation().isValid() || ignoreLocation(constructorDecl->getBeginLoc())) {
314 return true;
317 logCallToRootMethods(constructorDecl, callSet);
319 // Now do the checks necessary for the "can be private" analysis
320 if (constructorDecl->getParent() != currentCxxRecordDecl)
321 calledFromOutsideSet.insert(niceName(constructorDecl));
323 return true;
326 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
328 // ignore stuff that forms part of the stable URE interface
329 if (isInUnoIncludeFile(functionDecl)) {
330 return true;
332 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
333 if (isa<CXXDestructorDecl>(functionDecl)) {
334 return true;
336 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
337 return true;
339 if (isa<CXXConstructorDecl>(functionDecl)
340 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
342 return true;
344 if (!canonicalFunctionDecl->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl->getBeginLoc())) {
345 return true;
347 // ignore method overrides, since the call will show up as being directed to the root method
348 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
349 if (methodDecl && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>())) {
350 return true;
352 if (!functionDecl->isExternC()) {
353 MyFuncInfo funcInfo = niceName(canonicalFunctionDecl);
354 definitionSet.insert(funcInfo);
356 return true;
359 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
361 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
362 if (!functionDecl) {
363 return true;
365 logCallToRootMethods(functionDecl->getCanonicalDecl(), callSet);
366 logCallToRootMethods(functionDecl->getCanonicalDecl(), usedReturnSet);
368 // Now do the checks necessary for the "can be private" analysis
369 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
370 if (methodDecl && methodDecl->getAccess() != AS_private)
372 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(declRefExpr);
373 if (parentFunctionOfCallSite != functionDecl) {
374 if (!parentFunctionOfCallSite || !ignoreLocation(parentFunctionOfCallSite->getBeginLoc())) {
375 calledFromOutsideSet.insert(niceName(functionDecl));
380 return true;
383 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
385 auto copy = currentCxxRecordDecl;
386 currentCxxRecordDecl = cxxRecordDecl;
387 bool ret = RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
388 currentCxxRecordDecl = copy;
389 return ret;
392 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl* f)
394 auto copy = currentFunctionDecl;
395 currentFunctionDecl = f;
396 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f);
397 currentFunctionDecl = copy;
398 return ret;
400 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl* f)
402 auto copy = currentFunctionDecl;
403 currentFunctionDecl = f;
404 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f);
405 currentFunctionDecl = copy;
406 return ret;
408 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl* f)
410 auto copy = currentFunctionDecl;
411 currentFunctionDecl = f;
412 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f);
413 currentFunctionDecl = copy;
414 return ret;
416 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f)
418 auto copy = currentFunctionDecl;
419 currentFunctionDecl = f;
420 bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f);
421 currentFunctionDecl = copy;
422 return ret;
425 loplugin::Plugin::Registration< UnusedMethods > X("unusedmethods", false);
429 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */