LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / unusedmethods.cxx
blobccad7e3375d7aaceca1b0c304bb87c0998b703ba
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 COMPILER_PLUGIN_TOOL='unusedmethods' check
37 $ ./compilerplugins/clang/unusedmethods.py
39 and then
40 $ for dir in *; do make FORCE_COMPILE=all 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 StringRef fn(handler.getMainFileName());
86 // ignore external code, makes this run faster
87 if (fn.contains("UnpackedTarball"))
88 return;
90 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
92 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
93 // writing to the same logfile
95 std::string output;
96 for (const MyFuncInfo & s : definitionSet)
98 output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams
99 + "\t" + s.sourceLocation + "\t" + s.virtualness + "\n";
101 // for the "unused method" analysis
102 for (const MyFuncInfo & s : callSet)
103 output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
104 // for the "unused return type" analysis
105 for (const MyFuncInfo & s : usedReturnSet)
106 output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
107 // for the "method can be private" analysis
108 for (const MyFuncInfo & s : calledFromOutsideSet)
109 output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
110 std::ofstream myfile;
111 myfile.open( WORKDIR "/loplugin.unusedmethods.log", std::ios::app | std::ios::out);
112 myfile << output;
113 myfile.close();
116 bool shouldVisitTemplateInstantiations () const { return true; }
117 bool shouldVisitImplicitCode() const { return true; }
119 bool VisitCallExpr(CallExpr* );
120 bool VisitFunctionDecl( const FunctionDecl* decl );
121 bool VisitDeclRefExpr( const DeclRefExpr* );
122 bool VisitCXXConstructExpr( const CXXConstructExpr* );
123 bool TraverseCXXRecordDecl( CXXRecordDecl* );
124 bool TraverseFunctionDecl( FunctionDecl* );
125 bool TraverseCXXMethodDecl( CXXMethodDecl* );
126 bool TraverseCXXConversionDecl( CXXConversionDecl* );
127 bool TraverseCXXDeductionGuideDecl( CXXDeductionGuideDecl* );
129 private:
130 void logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet);
131 MyFuncInfo niceName(const FunctionDecl* functionDecl);
132 std::string toString(SourceLocation loc);
133 void functionTouchedFromExpr( const FunctionDecl* calleeFunctionDecl, const Expr* expr );
134 bool ignoreLocation(SourceLocation loc);
135 bool checkIgnoreLocation(SourceLocation loc);
137 CXXRecordDecl const * currentCxxRecordDecl = nullptr;
138 FunctionDecl const * currentFunctionDecl = nullptr;
141 MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
143 for(;;)
145 if (functionDecl->getInstantiatedFromMemberFunction())
146 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
147 #if CLANG_VERSION < 90000
148 else if (functionDecl->getClassScopeSpecializationPattern())
149 functionDecl = functionDecl->getClassScopeSpecializationPattern();
150 #endif
151 else if (functionDecl->getTemplateInstantiationPattern())
152 functionDecl = functionDecl->getTemplateInstantiationPattern();
153 else
154 break;
157 MyFuncInfo aInfo;
158 switch (functionDecl->getAccess())
160 case AS_public: aInfo.access = "public"; break;
161 case AS_private: aInfo.access = "private"; break;
162 case AS_protected: aInfo.access = "protected"; break;
163 default: aInfo.access = "unknown"; break;
165 if (!isa<CXXConstructorDecl>(functionDecl)) {
166 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
167 } else {
168 aInfo.returnType = "";
171 if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
172 const CXXRecordDecl* recordDecl = methodDecl->getParent();
173 aInfo.nameAndParams = recordDecl->getQualifiedNameAsString()
174 + "::"
175 + functionDecl->getNameAsString()
176 + "(";
177 if (methodDecl->isVirtual())
178 aInfo.virtualness = "virtual";
180 else
182 aInfo.nameAndParams = functionDecl->getQualifiedNameAsString() + "(";
184 bool bFirst = true;
185 for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
186 if (bFirst)
187 bFirst = false;
188 else
189 aInfo.nameAndParams += ",";
190 aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
192 aInfo.nameAndParams += ")";
193 if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
194 aInfo.nameAndParams += " const";
197 aInfo.sourceLocation = toString( functionDecl->getLocation() );
199 return aInfo;
203 * Our need to see everything conflicts with the PCH code in pluginhandler::ignoreLocation,
204 * so we have to do this ourselves.
206 bool UnusedMethods::ignoreLocation(SourceLocation loc)
208 static std::unordered_map<SourceLocation, bool> checkedMap;
209 auto it = checkedMap.find(loc);
210 if (it != checkedMap.end())
211 return it->second;
212 bool ignore = checkIgnoreLocation(loc);
213 checkedMap.emplace(loc, ignore);
214 return ignore;
217 bool UnusedMethods::checkIgnoreLocation(SourceLocation loc)
219 // simplified form of the code in PluginHandler::checkIgnoreLocation
220 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
221 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
222 return true;
223 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
224 if( presumedLoc.isInvalid())
225 return true;
226 const char* bufferName = presumedLoc.getFilename();
227 if (bufferName == NULL
228 || loplugin::hasPathnamePrefix(bufferName, SRCDIR "/external/")
229 || loplugin::hasPathnamePrefix(bufferName, WORKDIR "/"))
230 return true;
231 if( loplugin::hasPathnamePrefix(bufferName, BUILDDIR "/")
232 || loplugin::hasPathnamePrefix(bufferName, SRCDIR "/") )
233 return false; // ok
234 return true;
237 std::string UnusedMethods::toString(SourceLocation loc)
239 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
240 StringRef name = getFilenameOfLocation(expansionLoc);
241 std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
242 loplugin::normalizeDotDotInFilePath(sourceLocation);
243 return sourceLocation;
246 // For virtual/overriding methods, we need to pretend we called the root method(s),
247 // so that they get marked as used.
248 void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std::set<MyFuncInfo>& funcSet)
250 functionDecl = functionDecl->getCanonicalDecl();
251 bool bCalledSuperMethod = false;
252 if (isa<CXXMethodDecl>(functionDecl)) {
253 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
254 for(CXXMethodDecl::method_iterator it = methodDecl->begin_overridden_methods();
255 it != methodDecl->end_overridden_methods(); ++it)
257 logCallToRootMethods(*it, funcSet);
258 bCalledSuperMethod = true;
261 if (!bCalledSuperMethod)
263 while (functionDecl->getTemplateInstantiationPattern())
264 functionDecl = functionDecl->getTemplateInstantiationPattern();
265 if (functionDecl->getLocation().isValid() && !ignoreLocation( compat::getBeginLoc(functionDecl) )
266 && !functionDecl->isExternC())
267 funcSet.insert(niceName(functionDecl));
271 bool UnusedMethods::VisitCallExpr(CallExpr* expr)
273 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
274 // from template instantiation deep inside the STL and other external code
276 FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
277 if (calleeFunctionDecl == nullptr) {
278 Expr* callee = expr->getCallee()->IgnoreParenImpCasts();
279 DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
280 if (dr) {
281 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
282 if (calleeFunctionDecl)
283 goto gotfunc;
285 return true;
288 gotfunc:
291 if (currentFunctionDecl == calleeFunctionDecl)
292 ; // for "unused method" analysis, ignore recursive calls
293 else if (currentFunctionDecl
294 && currentFunctionDecl->getIdentifier()
295 && currentFunctionDecl->getName() == "Clone"
296 && currentFunctionDecl->getParent() == calleeFunctionDecl->getParent()
297 && isa<CXXConstructorDecl>(calleeFunctionDecl))
298 ; // if we are inside Clone(), ignore calls to the same class's constructor
299 else
300 logCallToRootMethods(calleeFunctionDecl, callSet);
302 const Stmt* parent = getParentStmt(expr);
304 // Now do the checks necessary for the "can be private" analysis
305 CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
306 if (calleeMethodDecl && calleeMethodDecl->getAccess() != AS_private)
308 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(expr);
309 if (parentFunctionOfCallSite != calleeFunctionDecl) {
310 if (!parentFunctionOfCallSite || !ignoreLocation(compat::getBeginLoc(parentFunctionOfCallSite))) {
311 calledFromOutsideSet.insert(niceName(calleeFunctionDecl));
316 // Now do the checks necessary for the "unused return value" analysis
317 if (calleeFunctionDecl->getReturnType()->isVoidType()) {
318 return true;
320 if (!parent) {
321 // we will get null parent if it's under a CXXConstructExpr node
322 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
323 return true;
325 if (isa<Expr>(parent) || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
326 || isa<IfStmt>(parent) || isa<SwitchStmt>(parent) || isa<ForStmt>(parent)
327 || isa<WhileStmt>(parent) || isa<DoStmt>(parent)
328 || isa<CXXForRangeStmt>(parent))
330 logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
331 return true;
333 if (isa<CompoundStmt>(parent) || isa<DefaultStmt>(parent) || isa<CaseStmt>(parent)
334 || isa<LabelStmt>(parent))
336 return true;
338 parent->dump();
339 return true;
342 bool UnusedMethods::VisitCXXConstructExpr( const CXXConstructExpr* constructExpr )
344 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
345 // from template instantiation deep inside the STL and other external code
347 const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor();
348 constructorDecl = constructorDecl->getCanonicalDecl();
350 if (!constructorDecl->getLocation().isValid() || ignoreLocation(compat::getBeginLoc(constructorDecl))) {
351 return true;
354 logCallToRootMethods(constructorDecl, callSet);
356 // Now do the checks necessary for the "can be private" analysis
357 if (constructorDecl->getParent() != currentCxxRecordDecl)
358 calledFromOutsideSet.insert(niceName(constructorDecl));
360 return true;
363 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
365 // ignore stuff that forms part of the stable URE interface
366 if (isInUnoIncludeFile(functionDecl)) {
367 return true;
369 const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
370 if (isa<CXXDestructorDecl>(functionDecl)) {
371 return true;
373 if (functionDecl->isDeleted() || functionDecl->isDefaulted()) {
374 return true;
376 if (isa<CXXConstructorDecl>(functionDecl)
377 && dyn_cast<CXXConstructorDecl>(functionDecl)->isCopyOrMoveConstructor())
379 return true;
381 if (!canonicalFunctionDecl->getLocation().isValid() || ignoreLocation(compat::getBeginLoc(canonicalFunctionDecl))) {
382 return true;
384 // ignore method overrides, since the call will show up as being directed to the root method
385 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
386 if (methodDecl && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>())) {
387 return true;
389 if (!functionDecl->isExternC()) {
390 MyFuncInfo funcInfo = niceName(canonicalFunctionDecl);
391 definitionSet.insert(funcInfo);
393 return true;
396 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
398 const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
399 if (!functionDecl) {
400 return true;
402 logCallToRootMethods(functionDecl->getCanonicalDecl(), callSet);
403 logCallToRootMethods(functionDecl->getCanonicalDecl(), usedReturnSet);
405 // Now do the checks necessary for the "can be private" analysis
406 const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
407 if (methodDecl && methodDecl->getAccess() != AS_private)
409 const FunctionDecl* parentFunctionOfCallSite = getParentFunctionDecl(declRefExpr);
410 if (parentFunctionOfCallSite != functionDecl) {
411 if (!parentFunctionOfCallSite || !ignoreLocation(compat::getBeginLoc(parentFunctionOfCallSite))) {
412 calledFromOutsideSet.insert(niceName(functionDecl));
417 return true;
420 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
422 auto copy = currentCxxRecordDecl;
423 currentCxxRecordDecl = cxxRecordDecl;
424 bool ret = RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
425 currentCxxRecordDecl = copy;
426 return ret;
429 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl* f)
431 auto copy = currentFunctionDecl;
432 currentFunctionDecl = f;
433 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f);
434 currentFunctionDecl = copy;
435 return ret;
437 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl* f)
439 auto copy = currentFunctionDecl;
440 currentFunctionDecl = f;
441 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f);
442 currentFunctionDecl = copy;
443 return ret;
445 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl* f)
447 auto copy = currentFunctionDecl;
448 currentFunctionDecl = f;
449 bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f);
450 currentFunctionDecl = copy;
451 return ret;
453 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f)
455 auto copy = currentFunctionDecl;
456 currentFunctionDecl = f;
457 bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f);
458 currentFunctionDecl = copy;
459 return ret;
462 loplugin::Plugin::Registration< UnusedMethods > X("unusedmethods", false);
466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */