1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
15 #include <unordered_map>
18 #include "clang/AST/Attr.h"
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:
36 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedmethods' check
37 $ ./compilerplugins/clang/unusedmethods.py
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
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
;
77 public RecursiveASTVisitor
<UnusedMethods
>, public loplugin::Plugin
80 explicit UnusedMethods(loplugin::InstantiationData
const & data
):
83 virtual void run() override
85 StringRef
fn(handler
.getMainFileName());
86 // ignore external code, makes this run faster
87 if (fn
.contains("UnpackedTarball"))
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
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
);
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
* );
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
)
145 if (functionDecl
->getInstantiatedFromMemberFunction())
146 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
147 #if CLANG_VERSION < 90000
148 else if (functionDecl
->getClassScopeSpecializationPattern())
149 functionDecl
= functionDecl
->getClassScopeSpecializationPattern();
151 else if (functionDecl
->getTemplateInstantiationPattern())
152 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
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();
168 aInfo
.returnType
= "";
171 if (auto methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)) {
172 const CXXRecordDecl
* recordDecl
= methodDecl
->getParent();
173 aInfo
.nameAndParams
= recordDecl
->getQualifiedNameAsString()
175 + functionDecl
->getNameAsString()
177 if (methodDecl
->isVirtual())
178 aInfo
.virtualness
= "virtual";
182 aInfo
.nameAndParams
= functionDecl
->getQualifiedNameAsString() + "(";
185 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
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() );
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())
212 bool ignore
= checkIgnoreLocation(loc
);
213 checkedMap
.emplace(loc
, 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
))
223 PresumedLoc presumedLoc
= compiler
.getSourceManager().getPresumedLoc( expansionLoc
);
224 if( presumedLoc
.isInvalid())
226 const char* bufferName
= presumedLoc
.getFilename();
227 if (bufferName
== NULL
228 || loplugin::hasPathnamePrefix(bufferName
, SRCDIR
"/external/")
229 || loplugin::hasPathnamePrefix(bufferName
, WORKDIR
"/"))
231 if( loplugin::hasPathnamePrefix(bufferName
, BUILDDIR
"/")
232 || loplugin::hasPathnamePrefix(bufferName
, SRCDIR
"/") )
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
);
281 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
282 if (calleeFunctionDecl
)
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
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()) {
321 // we will get null parent if it's under a CXXConstructExpr node
322 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
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
);
333 if (isa
<CompoundStmt
>(parent
) || isa
<DefaultStmt
>(parent
) || isa
<CaseStmt
>(parent
)
334 || isa
<LabelStmt
>(parent
))
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
))) {
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
));
363 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
365 // ignore stuff that forms part of the stable URE interface
366 if (isInUnoIncludeFile(functionDecl
)) {
369 const FunctionDecl
* canonicalFunctionDecl
= functionDecl
->getCanonicalDecl();
370 if (isa
<CXXDestructorDecl
>(functionDecl
)) {
373 if (functionDecl
->isDeleted() || functionDecl
->isDefaulted()) {
376 if (isa
<CXXConstructorDecl
>(functionDecl
)
377 && dyn_cast
<CXXConstructorDecl
>(functionDecl
)->isCopyOrMoveConstructor())
381 if (!canonicalFunctionDecl
->getLocation().isValid() || ignoreLocation(compat::getBeginLoc(canonicalFunctionDecl
))) {
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
>())) {
389 if (!functionDecl
->isExternC()) {
390 MyFuncInfo funcInfo
= niceName(canonicalFunctionDecl
);
391 definitionSet
.insert(funcInfo
);
396 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
398 const FunctionDecl
* functionDecl
= dyn_cast
<FunctionDecl
>(declRefExpr
->getDecl());
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
));
420 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl
* cxxRecordDecl
)
422 auto copy
= currentCxxRecordDecl
;
423 currentCxxRecordDecl
= cxxRecordDecl
;
424 bool ret
= RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl
);
425 currentCxxRecordDecl
= copy
;
429 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl
* f
)
431 auto copy
= currentFunctionDecl
;
432 currentFunctionDecl
= f
;
433 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(f
);
434 currentFunctionDecl
= copy
;
437 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
439 auto copy
= currentFunctionDecl
;
440 currentFunctionDecl
= f
;
441 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(f
);
442 currentFunctionDecl
= copy
;
445 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl
* f
)
447 auto copy
= currentFunctionDecl
;
448 currentFunctionDecl
= f
;
449 bool ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(f
);
450 currentFunctionDecl
= copy
;
453 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* f
)
455 auto copy
= currentFunctionDecl
;
456 currentFunctionDecl
= f
;
457 bool ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f
);
458 currentFunctionDecl
= copy
;
462 loplugin::Plugin::Registration
< UnusedMethods
> X("unusedmethods", false);
466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */