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/.
19 This performs two analyses:
20 (1) look for unused fields
21 (2) look for fields that are write-only
23 We dmp a list of calls to methods, and a list of field definitions.
24 Then we will post-process the 2 lists and find the set of unused methods.
26 Be warned that it produces around 5G of log file.
28 The process goes something like this:
30 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedfields' check
31 $ ./compilerplugins/clang/unusedfields.py
34 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done
35 to auto-remove the method declarations
37 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
46 std::string parentClass
;
47 std::string fieldName
;
48 std::string fieldType
;
49 std::string sourceLocation
;
52 bool operator < (const MyFieldInfo
&lhs
, const MyFieldInfo
&rhs
)
54 return std::tie(lhs
.parentClass
, lhs
.fieldName
)
55 < std::tie(rhs
.parentClass
, rhs
.fieldName
);
59 // try to limit the voluminous output a little
60 static std::set
<MyFieldInfo
> touchedSet
;
61 static std::set
<MyFieldInfo
> touchedFromOutsideSet
;
62 static std::set
<MyFieldInfo
> readFromSet
;
63 static std::set
<MyFieldInfo
> definitionSet
;
67 public RecursiveASTVisitor
<UnusedFields
>, public loplugin::Plugin
70 explicit UnusedFields(InstantiationData
const & data
): Plugin(data
) {}
72 virtual void run() override
74 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
76 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
77 // writing to the same logfile
79 for (const MyFieldInfo
& s
: touchedSet
)
80 output
+= "touch:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
81 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
82 output
+= "outside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
83 for (const MyFieldInfo
& s
: readFromSet
)
84 output
+= "read:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
85 for (const MyFieldInfo
& s
: definitionSet
)
87 output
+= "definition:\t" + s
.access
+ "\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.fieldType
+ "\t" + s
.sourceLocation
+ "\n";
90 myfile
.open( SRCDIR
"/loplugin.unusedfields.log", ios::app
| ios::out
);
95 bool shouldVisitTemplateInstantiations () const { return true; }
96 bool shouldVisitImplicitCode() const { return true; }
98 bool VisitFieldDecl( const FieldDecl
* );
99 bool VisitMemberExpr( const MemberExpr
* );
100 bool VisitDeclRefExpr( const DeclRefExpr
* );
102 MyFieldInfo
niceName(const FieldDecl
*);
103 void checkForTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
, const MyFieldInfo
& fieldInfo
);
106 MyFieldInfo
UnusedFields::niceName(const FieldDecl
* fieldDecl
)
110 const RecordDecl
* recordDecl
= fieldDecl
->getParent();
112 if (const CXXRecordDecl
* cxxRecordDecl
= dyn_cast
<CXXRecordDecl
>(recordDecl
))
114 if (cxxRecordDecl
->getTemplateInstantiationPattern())
115 cxxRecordDecl
= cxxRecordDecl
->getTemplateInstantiationPattern();
116 aInfo
.parentClass
= cxxRecordDecl
->getQualifiedNameAsString();
119 aInfo
.parentClass
= recordDecl
->getQualifiedNameAsString();
121 aInfo
.fieldName
= fieldDecl
->getNameAsString();
122 aInfo
.fieldType
= fieldDecl
->getType().getAsString();
124 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( fieldDecl
->getLocation() );
125 StringRef name
= compiler
.getSourceManager().getFilename(expansionLoc
);
126 aInfo
.sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
127 normalizeDotDotInFilePath(aInfo
.sourceLocation
);
129 switch (fieldDecl
->getAccess())
131 case AS_public
: aInfo
.access
= "public"; break;
132 case AS_private
: aInfo
.access
= "private"; break;
133 case AS_protected
: aInfo
.access
= "protected"; break;
134 default: aInfo
.access
= "unknown"; break;
140 bool UnusedFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
142 fieldDecl
= fieldDecl
->getCanonicalDecl();
143 if (ignoreLocation( fieldDecl
)) {
146 // ignore stuff that forms part of the stable URE interface
147 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
151 QualType type
= fieldDecl
->getType();
152 // unwrap array types
153 while (type
->isArrayType())
154 type
= type
->getAsArrayTypeUnsafe()->getElementType();
156 if( CXXRecordDecl* recordDecl = type->getAsCXXRecordDecl() )
158 bool warn_unused = recordDecl->hasAttr<WarnUnusedAttr>();
161 string n = recordDecl->getQualifiedNameAsString();
162 // Check some common non-LO types.
163 if( n == "std::string" || n == "std::basic_string"
164 || n == "std::list" || n == "std::__debug::list"
165 || n == "std::vector" || n == "std::__debug::vector" )
172 definitionSet
.insert(niceName(fieldDecl
));
176 bool UnusedFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
178 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
179 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
183 fieldDecl
= fieldDecl
->getCanonicalDecl();
184 if (ignoreLocation(fieldDecl
)) {
187 // ignore stuff that forms part of the stable URE interface
188 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
192 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
194 // for the touched-from-outside analysis
196 checkForTouchedFromOutside(fieldDecl
, memberExpr
, fieldInfo
);
198 // for the write-only analysis
200 const Stmt
* child
= memberExpr
;
201 const Stmt
* parent
= parentStmt(memberExpr
);
202 // walk up the tree until we find something interesting
203 bool bPotentiallyReadFrom
= false;
209 if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
210 || isa
<ExprWithCleanups
>(parent
) || isa
<UnaryOperator
>(parent
))
213 parent
= parentStmt(parent
);
215 else if (isa
<CaseStmt
>(parent
))
217 bPotentiallyReadFrom
= dyn_cast
<CaseStmt
>(parent
)->getLHS() == child
218 || dyn_cast
<CaseStmt
>(parent
)->getRHS() == child
;
221 else if (isa
<IfStmt
>(parent
))
223 bPotentiallyReadFrom
= dyn_cast
<IfStmt
>(parent
)->getCond() == child
;
226 else if (isa
<DoStmt
>(parent
))
228 bPotentiallyReadFrom
= dyn_cast
<DoStmt
>(parent
)->getCond() == child
;
231 else if (isa
<ReturnStmt
>(parent
) || isa
<CXXConstructExpr
>(parent
) || isa
<CallExpr
>(parent
)
232 || isa
<ConditionalOperator
>(parent
) || isa
<SwitchStmt
>(parent
) || isa
<ArraySubscriptExpr
>(parent
)
233 || isa
<DeclStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<CXXNewExpr
>(parent
)
234 || isa
<ForStmt
>(parent
) || isa
<InitListExpr
>(parent
)
235 || isa
<BinaryOperator
>(parent
) || isa
<CXXDependentScopeMemberExpr
>(parent
)
236 || isa
<UnresolvedMemberExpr
>(parent
)
237 || isa
<MaterializeTemporaryExpr
>(parent
)) //???
239 bPotentiallyReadFrom
= true;
242 else if (isa
<CXXDeleteExpr
>(parent
)
243 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
244 || isa
<CXXUnresolvedConstructExpr
>(parent
) || isa
<CompoundStmt
>(parent
)
245 || isa
<CXXTypeidExpr
>(parent
) || isa
<DefaultStmt
>(parent
))
250 bPotentiallyReadFrom
= true;
258 DiagnosticsEngine::Warning
,
259 "oh dear, what can the matter be?",
260 memberExpr
->getLocStart())
261 << memberExpr
->getSourceRange();
264 if (bPotentiallyReadFrom
)
265 readFromSet
.insert(fieldInfo
);
269 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
271 const Decl
* decl
= declRefExpr
->getDecl();
272 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
276 fieldDecl
= fieldDecl
->getCanonicalDecl();
277 if (ignoreLocation(fieldDecl
)) {
280 // ignore stuff that forms part of the stable URE interface
281 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
284 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
285 touchedSet
.insert(fieldInfo
);
286 checkForTouchedFromOutside(fieldDecl
, declRefExpr
, fieldInfo
);
290 void UnusedFields::checkForTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
, const MyFieldInfo
& fieldInfo
) {
291 const FunctionDecl
* memberExprParentFunction
= parentFunctionDecl(memberExpr
);
292 const CXXMethodDecl
* methodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberExprParentFunction
);
294 // it's touched from somewhere outside a class
296 touchedSet
.insert(fieldInfo
);
297 touchedFromOutsideSet
.insert(fieldInfo
);
301 auto constructorDecl
= dyn_cast
<CXXConstructorDecl
>(methodDecl
);
302 if (methodDecl
->isCopyAssignmentOperator() || methodDecl
->isMoveAssignmentOperator()) {
303 // ignore move/copy operator, it's self->self
304 } else if (constructorDecl
&& (constructorDecl
->isCopyConstructor() || constructorDecl
->isMoveConstructor())) {
305 // ignore move/copy constructor, it's self->self
307 touchedSet
.insert(fieldInfo
);
308 if (memberExprParentFunction
->getParent() != fieldDecl
->getParent()) {
309 touchedFromOutsideSet
.insert(fieldInfo
);
314 loplugin::Plugin::Registration
< UnusedFields
> X("unusedfields", false);
318 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */