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"
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:
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedmethods' check
38 $ ./compilerplugins/clang/unusedmethods.py
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
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
;
78 public RecursiveASTVisitor
<UnusedMethods
>, public loplugin::Plugin
81 explicit UnusedMethods(loplugin::InstantiationData
const & 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
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
);
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
* );
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();
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 (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() + "(";
165 for (const ParmVarDecl
*pParmVarDecl
: compat::parameters(*functionDecl
)) {
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() );
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
);
226 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
227 if (calleeFunctionDecl
)
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
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()) {
266 // we will get null parent if it's under a CXXConstructExpr node
267 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
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
);
278 if (isa
<CompoundStmt
>(parent
) || isa
<DefaultStmt
>(parent
) || isa
<CaseStmt
>(parent
)
279 || isa
<LabelStmt
>(parent
))
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
)) {
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
));
308 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
310 // ignore stuff that forms part of the stable URE interface
311 if (isInUnoIncludeFile(functionDecl
)) {
314 const FunctionDecl
* canonicalFunctionDecl
= functionDecl
->getCanonicalDecl();
315 if (isa
<CXXDestructorDecl
>(functionDecl
)) {
318 if (functionDecl
->isDeleted() || functionDecl
->isDefaulted()) {
321 if (isa
<CXXConstructorDecl
>(functionDecl
)
322 && dyn_cast
<CXXConstructorDecl
>(functionDecl
)->isCopyOrMoveConstructor())
326 if (!canonicalFunctionDecl
->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl
)) {
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
>())) {
334 if (!functionDecl
->isExternC()) {
335 MyFuncInfo funcInfo
= niceName(canonicalFunctionDecl
);
336 definitionSet
.insert(funcInfo
);
341 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
343 const FunctionDecl
* functionDecl
= dyn_cast
<FunctionDecl
>(declRefExpr
->getDecl());
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
));
365 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl
* cxxRecordDecl
)
367 auto copy
= currentCxxRecordDecl
;
368 currentCxxRecordDecl
= cxxRecordDecl
;
369 bool ret
= RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl
);
370 currentCxxRecordDecl
= copy
;
374 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl
* f
)
376 auto copy
= currentFunctionDecl
;
377 currentFunctionDecl
= f
;
378 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(f
);
379 currentFunctionDecl
= copy
;
382 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
384 auto copy
= currentFunctionDecl
;
385 currentFunctionDecl
= f
;
386 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(f
);
387 currentFunctionDecl
= copy
;
390 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl
* f
)
392 auto copy
= currentFunctionDecl
;
393 currentFunctionDecl
= f
;
394 bool ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(f
);
395 currentFunctionDecl
= copy
;
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
;
409 loplugin::Plugin::Registration
< UnusedMethods
> X("unusedmethods", false);
413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */