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"
20 #include "config_clang.h"
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:
38 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedmethods' check
39 $ ./compilerplugins/clang/unusedmethods.py
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
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
;
79 public RecursiveASTVisitor
<UnusedMethods
>, public loplugin::Plugin
82 explicit UnusedMethods(loplugin::InstantiationData
const & 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"))
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
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
);
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
* );
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
)
147 if (functionDecl
->getInstantiatedFromMemberFunction())
148 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
149 else if (functionDecl
->getTemplateInstantiationPattern())
150 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
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();
166 aInfo
.returnType
= "";
169 if (auto methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)) {
170 const CXXRecordDecl
* recordDecl
= methodDecl
->getParent();
171 aInfo
.nameAndParams
= recordDecl
->getQualifiedNameAsString()
173 + functionDecl
->getNameAsString()
175 if (methodDecl
->isVirtual())
176 aInfo
.virtualness
= "virtual";
180 aInfo
.nameAndParams
= functionDecl
->getQualifiedNameAsString() + "(";
183 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
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() );
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
);
244 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
245 if (calleeFunctionDecl
)
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
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()) {
284 // we will get null parent if it's under a CXXConstructExpr node
285 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
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
);
296 if (isa
<CompoundStmt
>(parent
) || isa
<DefaultStmt
>(parent
) || isa
<CaseStmt
>(parent
)
297 || isa
<LabelStmt
>(parent
))
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())) {
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
));
326 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
328 // ignore stuff that forms part of the stable URE interface
329 if (isInUnoIncludeFile(functionDecl
)) {
332 const FunctionDecl
* canonicalFunctionDecl
= functionDecl
->getCanonicalDecl();
333 if (isa
<CXXDestructorDecl
>(functionDecl
)) {
336 if (functionDecl
->isDeleted() || functionDecl
->isDefaulted()) {
339 if (isa
<CXXConstructorDecl
>(functionDecl
)
340 && dyn_cast
<CXXConstructorDecl
>(functionDecl
)->isCopyOrMoveConstructor())
344 if (!canonicalFunctionDecl
->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl
->getBeginLoc())) {
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
>())) {
352 if (!functionDecl
->isExternC()) {
353 MyFuncInfo funcInfo
= niceName(canonicalFunctionDecl
);
354 definitionSet
.insert(funcInfo
);
359 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
361 const FunctionDecl
* functionDecl
= dyn_cast
<FunctionDecl
>(declRefExpr
->getDecl());
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
));
383 bool UnusedMethods::TraverseCXXRecordDecl(CXXRecordDecl
* cxxRecordDecl
)
385 auto copy
= currentCxxRecordDecl
;
386 currentCxxRecordDecl
= cxxRecordDecl
;
387 bool ret
= RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl
);
388 currentCxxRecordDecl
= copy
;
392 bool UnusedMethods::TraverseFunctionDecl(FunctionDecl
* f
)
394 auto copy
= currentFunctionDecl
;
395 currentFunctionDecl
= f
;
396 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(f
);
397 currentFunctionDecl
= copy
;
400 bool UnusedMethods::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
402 auto copy
= currentFunctionDecl
;
403 currentFunctionDecl
= f
;
404 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(f
);
405 currentFunctionDecl
= copy
;
408 bool UnusedMethods::TraverseCXXConversionDecl(CXXConversionDecl
* f
)
410 auto copy
= currentFunctionDecl
;
411 currentFunctionDecl
= f
;
412 bool ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(f
);
413 currentFunctionDecl
= copy
;
416 bool UnusedMethods::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* f
)
418 auto copy
= currentFunctionDecl
;
419 currentFunctionDecl
= f
;
420 bool ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f
);
421 currentFunctionDecl
= copy
;
425 loplugin::Plugin::Registration
< UnusedMethods
> X("unusedmethods", false);
429 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */