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(InstantiationData
const & data
): Plugin(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";
104 myfile
.open( SRCDIR
"/loplugin.unusedmethods.log", ios::app
| 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
* );
117 void logCallToRootMethods(const FunctionDecl
* functionDecl
, std::set
<MyFuncInfo
>& funcSet
);
118 MyFuncInfo
niceName(const FunctionDecl
* functionDecl
);
119 std::string
toString(SourceLocation loc
);
120 void functionTouchedFromExpr( const FunctionDecl
* calleeFunctionDecl
, const Expr
* expr
);
123 MyFuncInfo
UnusedMethods::niceName(const FunctionDecl
* functionDecl
)
125 if (functionDecl
->getInstantiatedFromMemberFunction())
126 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
127 else if (functionDecl
->getClassScopeSpecializationPattern())
128 functionDecl
= functionDecl
->getClassScopeSpecializationPattern();
129 // workaround clang-3.5 issue
130 #if CLANG_VERSION >= 30600
131 else if (functionDecl
->getTemplateInstantiationPattern())
132 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
136 switch (functionDecl
->getAccess())
138 case AS_public
: aInfo
.access
= "public"; break;
139 case AS_private
: aInfo
.access
= "private"; break;
140 case AS_protected
: aInfo
.access
= "protected"; break;
141 default: aInfo
.access
= "unknown"; break;
143 if (!isa
<CXXConstructorDecl
>(functionDecl
)) {
144 aInfo
.returnType
= compat::getReturnType(*functionDecl
).getCanonicalType().getAsString();
146 aInfo
.returnType
= "";
149 if (const CXXMethodDecl
* methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)) {
150 const CXXRecordDecl
* recordDecl
= methodDecl
->getParent();
151 aInfo
.nameAndParams
+= recordDecl
->getQualifiedNameAsString();
152 aInfo
.nameAndParams
+= "::";
153 if (methodDecl
->isVirtual())
154 aInfo
.virtualness
= "virtual";
156 aInfo
.nameAndParams
+= functionDecl
->getNameAsString() + "(";
158 for (const ParmVarDecl
*pParmVarDecl
: compat::parameters(*functionDecl
)) {
162 aInfo
.nameAndParams
+= ",";
163 aInfo
.nameAndParams
+= pParmVarDecl
->getType().getCanonicalType().getAsString();
165 aInfo
.nameAndParams
+= ")";
166 if (isa
<CXXMethodDecl
>(functionDecl
) && dyn_cast
<CXXMethodDecl
>(functionDecl
)->isConst()) {
167 aInfo
.nameAndParams
+= " const";
170 aInfo
.sourceLocation
= toString( functionDecl
->getLocation() );
175 std::string
UnusedMethods::toString(SourceLocation loc
)
177 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( loc
);
178 StringRef name
= compiler
.getSourceManager().getFilename(expansionLoc
);
179 std::string sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
180 normalizeDotDotInFilePath(sourceLocation
);
181 return sourceLocation
;
184 // For virtual/overriding methods, we need to pretend we called the root method(s),
185 // so that they get marked as used.
186 void UnusedMethods::logCallToRootMethods(const FunctionDecl
* functionDecl
, std::set
<MyFuncInfo
>& funcSet
)
188 functionDecl
= functionDecl
->getCanonicalDecl();
189 bool bCalledSuperMethod
= false;
190 if (isa
<CXXMethodDecl
>(functionDecl
)) {
191 const CXXMethodDecl
* methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
192 for(CXXMethodDecl::method_iterator it
= methodDecl
->begin_overridden_methods();
193 it
!= methodDecl
->end_overridden_methods(); ++it
)
195 logCallToRootMethods(*it
, funcSet
);
196 bCalledSuperMethod
= true;
199 if (!bCalledSuperMethod
)
201 while (functionDecl
->getTemplateInstantiationPattern())
202 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
203 if (functionDecl
->getLocation().isValid() && !ignoreLocation( functionDecl
)
204 && !functionDecl
->isExternC())
205 funcSet
.insert(niceName(functionDecl
));
209 bool UnusedMethods::VisitCallExpr(CallExpr
* expr
)
211 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
212 // from template instantiation deep inside the STL and other external code
214 FunctionDecl
* calleeFunctionDecl
= expr
->getDirectCallee();
215 if (calleeFunctionDecl
== nullptr) {
216 Expr
* callee
= expr
->getCallee()->IgnoreParenImpCasts();
217 DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
219 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
220 if (calleeFunctionDecl
)
225 throw "Can't touch this";
231 logCallToRootMethods(calleeFunctionDecl
, callSet
);
233 const Stmt
* parent
= parentStmt(expr
);
235 // Now do the checks necessary for the "can be private" analysis
236 CXXMethodDecl
* calleeMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
);
237 if (calleeMethodDecl
&& calleeMethodDecl
->getAccess() != AS_private
)
239 const FunctionDecl
* parentFunctionOfCallSite
= parentFunctionDecl(expr
);
240 if (parentFunctionOfCallSite
!= calleeFunctionDecl
) {
241 if (!parentFunctionOfCallSite
|| !ignoreLocation(parentFunctionOfCallSite
)) {
242 calledFromOutsideSet
.insert(niceName(calleeFunctionDecl
));
247 // Now do the checks necessary for the "unused return value" analysis
248 if (compat::getReturnType(*calleeFunctionDecl
)->isVoidType()) {
252 // we will get null parent if it's under a CXXConstructExpr node
253 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
256 if (isa
<Expr
>(parent
) || isa
<ReturnStmt
>(parent
) || isa
<DeclStmt
>(parent
)
257 || isa
<IfStmt
>(parent
) || isa
<SwitchStmt
>(parent
) || isa
<ForStmt
>(parent
)
258 || isa
<WhileStmt
>(parent
) || isa
<DoStmt
>(parent
)
259 || isa
<CXXForRangeStmt
>(parent
))
261 logCallToRootMethods(calleeFunctionDecl
, usedReturnSet
);
264 if (isa
<CompoundStmt
>(parent
) || isa
<DefaultStmt
>(parent
) || isa
<CaseStmt
>(parent
)
265 || isa
<LabelStmt
>(parent
))
273 bool UnusedMethods::VisitCXXConstructExpr( const CXXConstructExpr
* constructExpr
)
275 // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
276 // from template instantiation deep inside the STL and other external code
278 const CXXConstructorDecl
* constructorDecl
= constructExpr
->getConstructor();
279 constructorDecl
= constructorDecl
->getCanonicalDecl();
281 if (!constructorDecl
->getLocation().isValid() || ignoreLocation(constructorDecl
)) {
285 logCallToRootMethods(constructorDecl
, callSet
);
290 bool UnusedMethods::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
292 // ignore stuff that forms part of the stable URE interface
293 if (isInUnoIncludeFile(functionDecl
)) {
296 const FunctionDecl
* canonicalFunctionDecl
= functionDecl
->getCanonicalDecl();
297 if (isa
<CXXDestructorDecl
>(functionDecl
)) {
300 if (functionDecl
->isDeleted() || functionDecl
->isDefaulted()) {
303 if (isa
<CXXConstructorDecl
>(functionDecl
)
304 && dyn_cast
<CXXConstructorDecl
>(functionDecl
)->isCopyOrMoveConstructor())
308 if (!canonicalFunctionDecl
->getLocation().isValid() || ignoreLocation(canonicalFunctionDecl
)) {
311 // ignore method overrides, since the call will show up as being directed to the root method
312 const CXXMethodDecl
* methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
313 if (methodDecl
&& (methodDecl
->size_overridden_methods() != 0 || methodDecl
->hasAttr
<OverrideAttr
>())) {
316 if (!functionDecl
->isExternC()) {
317 MyFuncInfo funcInfo
= niceName(canonicalFunctionDecl
);
318 definitionSet
.insert(funcInfo
);
323 bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
325 const FunctionDecl
* functionDecl
= dyn_cast
<FunctionDecl
>(declRefExpr
->getDecl());
329 logCallToRootMethods(functionDecl
->getCanonicalDecl(), callSet
);
330 logCallToRootMethods(functionDecl
->getCanonicalDecl(), usedReturnSet
);
332 // Now do the checks necessary for the "can be private" analysis
333 const CXXMethodDecl
* methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
334 if (methodDecl
&& methodDecl
->getAccess() != AS_private
)
336 const FunctionDecl
* parentFunctionOfCallSite
= parentFunctionDecl(declRefExpr
);
337 if (parentFunctionOfCallSite
!= functionDecl
) {
338 if (!parentFunctionOfCallSite
|| !ignoreLocation(parentFunctionOfCallSite
)) {
339 calledFromOutsideSet
.insert(niceName(functionDecl
));
347 loplugin::Plugin::Registration
< UnusedMethods
> X("unusedmethods", false);
351 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */