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=1 COMPILER_PLUGIN_TOOL='unusedmethods' check
37 $ ./compilerplugins/clang/unusedmethods.py
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
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 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
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
);
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
* );
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();
139 else if (functionDecl
->getTemplateInstantiationPattern())
140 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
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();
153 aInfo
.returnType
= "";
156 if (auto methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)) {
157 const CXXRecordDecl
* recordDecl
= methodDecl
->getParent();
158 aInfo
.nameAndParams
= recordDecl
->getQualifiedNameAsString()
160 + functionDecl
->getNameAsString()
162 if (methodDecl
->isVirtual())
163 aInfo
.virtualness
= "virtual";
167 aInfo
.nameAndParams
= functionDecl
->getQualifiedNameAsString() + "(";
170 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
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() );
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
);
231 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
232 if (calleeFunctionDecl
)
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
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()) {
271 // we will get null parent if it's under a CXXConstructExpr node
272 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
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
);
283 if (isa
<CompoundStmt
>(parent
) || isa
<DefaultStmt
>(parent
) || isa
<CaseStmt
>(parent
)
284 || isa
<LabelStmt
>(parent
))
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
)) {
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
));
313 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
315 // ignore stuff that forms part of the stable URE interface
316 if (isInUnoIncludeFile(functionDecl
)) {
319 const FunctionDecl
* canonicalFunctionDecl
= functionDecl
->getCanonicalDecl();
320 if (isa
<CXXDestructorDecl
>(functionDecl
)) {
323 if (functionDecl
->isDeleted() || functionDecl
->isDefaulted()) {
326 if (isa
<CXXConstructorDecl
>(functionDecl
)
327 && dyn_cast
<CXXConstructorDecl
>(functionDecl
)->isCopyOrMoveConstructor())
331 if (!canonicalFunctionDecl
->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl
)) {
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
>())) {
339 if (!functionDecl
->isExternC()) {
340 MyFuncInfo funcInfo
= niceName(canonicalFunctionDecl
);
341 definitionSet
.insert(funcInfo
);
346 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
348 const FunctionDecl
* functionDecl
= dyn_cast
<FunctionDecl
>(declRefExpr
->getDecl());
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
));
370 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl
* cxxRecordDecl
)
372 auto copy
= currentCxxRecordDecl
;
373 currentCxxRecordDecl
= cxxRecordDecl
;
374 bool ret
= RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl
);
375 currentCxxRecordDecl
= copy
;
379 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl
* f
)
381 auto copy
= currentFunctionDecl
;
382 currentFunctionDecl
= f
;
383 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(f
);
384 currentFunctionDecl
= copy
;
387 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
389 auto copy
= currentFunctionDecl
;
390 currentFunctionDecl
= f
;
391 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(f
);
392 currentFunctionDecl
= copy
;
395 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl
* f
)
397 auto copy
= currentFunctionDecl
;
398 currentFunctionDecl
= f
;
399 bool ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(f
);
400 currentFunctionDecl
= copy
;
403 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* f
)
405 auto copy
= currentFunctionDecl
;
406 currentFunctionDecl
= f
;
407 bool ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f
);
408 currentFunctionDecl
= copy
;
412 loplugin::Plugin::Registration
< UnusedMethods
> X("unusedmethods", false);
416 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */