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/.
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
16 #include <unordered_set>
22 #include "config_clang.h"
28 #include "clang/AST/ParentMapContext.h"
31 This performs two analyses:
32 (1) look for unused fields
33 (2) look for fields that are write-only
35 We dmp a list of calls to methods, and a list of field definitions.
36 Then we will post-process the 2 lists and find the set of unused methods.
38 Be warned that it produces around 5G of log file.
40 The process goes something like this:
42 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedfields' check
43 $ ./compilerplugins/clang/unusedfields.py
46 $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done
47 to auto-remove the method declarations
49 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
58 const RecordDecl
* parentRecord
;
59 std::string parentClass
;
60 std::string fieldName
;
61 std::string fieldType
;
62 std::string sourceLocation
;
65 bool operator < (const MyFieldInfo
&lhs
, const MyFieldInfo
&rhs
)
67 return std::tie(lhs
.parentClass
, lhs
.fieldName
)
68 < std::tie(rhs
.parentClass
, rhs
.fieldName
);
72 // try to limit the voluminous output a little
73 static std::set
<MyFieldInfo
> touchedFromInsideSet
;
74 static std::set
<MyFieldInfo
> touchedFromOutsideSet
;
75 static std::set
<MyFieldInfo
> touchedFromOutsideConstructorSet
;
76 static std::set
<MyFieldInfo
> readFromSet
;
77 static std::set
<MyFieldInfo
> writeToSet
;
78 static std::set
<MyFieldInfo
> definitionSet
;
81 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
85 const CallExpr
* m_callExpr
;
86 const CXXConstructExpr
* m_cxxConstructExpr
;
88 CallerWrapper(const CallExpr
* callExpr
) : m_callExpr(callExpr
), m_cxxConstructExpr(nullptr) {}
89 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
) : m_callExpr(nullptr), m_cxxConstructExpr(cxxConstructExpr
) {}
90 unsigned getNumArgs () const
91 { return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs(); }
92 const Expr
* getArg (unsigned i
) const
93 { return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
); }
97 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
98 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
99 const FunctionProtoType
* m_functionPrototype
= nullptr;
101 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
) : m_calleeFunctionDecl(calleeFunctionDecl
) {}
102 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
) : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor()) {}
103 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
) : m_functionPrototype(functionPrototype
) {}
104 unsigned getNumParams() const
106 if (m_calleeFunctionDecl
)
107 return m_calleeFunctionDecl
->getNumParams();
108 else if (m_cxxConstructorDecl
)
109 return m_cxxConstructorDecl
->getNumParams();
110 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
111 // FunctionProtoType will assert if we call getParamTypes() and it has no params
114 return m_functionPrototype
->getParamTypes().size();
116 const QualType
getParamType(unsigned i
) const
118 if (m_calleeFunctionDecl
)
119 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
120 else if (m_cxxConstructorDecl
)
121 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
123 return m_functionPrototype
->getParamTypes()[i
];
125 std::string
getNameAsString() const
127 if (m_calleeFunctionDecl
)
128 return m_calleeFunctionDecl
->getNameAsString();
129 else if (m_cxxConstructorDecl
)
130 return m_cxxConstructorDecl
->getNameAsString();
134 CXXMethodDecl
const * getAsCXXMethodDecl() const
136 if (m_calleeFunctionDecl
)
137 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
143 public loplugin::FilteringPlugin
<UnusedFields
>
146 explicit UnusedFields(loplugin::InstantiationData
const & data
):
147 FilteringPlugin(data
) {}
149 virtual void run() override
;
151 bool shouldVisitTemplateInstantiations () const { return true; }
152 bool shouldVisitImplicitCode() const { return true; }
154 bool VisitFieldDecl( const FieldDecl
* );
155 bool VisitMemberExpr( const MemberExpr
* );
156 bool VisitDeclRefExpr( const DeclRefExpr
* );
157 bool VisitCXXConstructorDecl( const CXXConstructorDecl
* );
158 bool VisitInitListExpr( const InitListExpr
* );
159 bool TraverseCXXConstructorDecl( CXXConstructorDecl
* );
160 bool TraverseCXXMethodDecl( CXXMethodDecl
* );
161 bool TraverseFunctionDecl( FunctionDecl
* );
162 bool TraverseIfStmt( IfStmt
* );
165 MyFieldInfo
niceName(const FieldDecl
*);
166 void checkTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
167 void checkIfReadFrom(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
168 void checkIfWrittenTo(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
169 bool isSomeKindOfZero(const Expr
* arg
);
170 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
);
171 bool IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
172 CalleeWrapper calleeFunctionDecl
);
173 compat::optional
<CalleeWrapper
> getCallee(CallExpr
const *);
175 RecordDecl
* insideMoveOrCopyOrCloneDeclParent
= nullptr;
176 RecordDecl
* insideStreamOutputOperator
= nullptr;
177 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
178 // we store the parent function on the way down the AST.
179 FunctionDecl
* insideFunctionDecl
= nullptr;
180 std::vector
<FieldDecl
const *> insideConditionalCheckOfMemberSet
;
183 void UnusedFields::run()
185 handler
.enableTreeWideAnalysisMode();
187 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
189 if (!isUnitTestMode())
191 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
192 // writing to the same logfile
194 for (const MyFieldInfo
& s
: touchedFromInsideSet
)
195 output
+= "inside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
196 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
197 output
+= "outside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
198 for (const MyFieldInfo
& s
: touchedFromOutsideConstructorSet
)
199 output
+= "outside-constructor:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
200 for (const MyFieldInfo
& s
: readFromSet
)
201 output
+= "read:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
202 for (const MyFieldInfo
& s
: writeToSet
)
203 output
+= "write:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
204 for (const MyFieldInfo
& s
: definitionSet
)
205 output
+= "definition:\t" + s
.access
+ "\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.fieldType
+ "\t" + s
.sourceLocation
+ "\n";
206 std::ofstream myfile
;
207 myfile
.open( WORKDIR
"/loplugin.unusedfields.log", std::ios::app
| std::ios::out
);
213 for (const MyFieldInfo
& s
: readFromSet
)
215 DiagnosticsEngine::Warning
,
217 s
.parentRecord
->getBeginLoc())
219 for (const MyFieldInfo
& s
: writeToSet
)
221 DiagnosticsEngine::Warning
,
223 s
.parentRecord
->getBeginLoc())
225 for (const MyFieldInfo
& s
: touchedFromOutsideConstructorSet
)
227 DiagnosticsEngine::Warning
,
228 "outside-constructor %0",
229 s
.parentRecord
->getBeginLoc())
231 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
233 DiagnosticsEngine::Warning
,
235 s
.parentRecord
->getBeginLoc())
241 MyFieldInfo
UnusedFields::niceName(const FieldDecl
* fieldDecl
)
245 const RecordDecl
* recordDecl
= fieldDecl
->getParent();
247 if (const CXXRecordDecl
* cxxRecordDecl
= dyn_cast
<CXXRecordDecl
>(recordDecl
))
249 if (cxxRecordDecl
->getTemplateInstantiationPattern())
250 cxxRecordDecl
= cxxRecordDecl
->getTemplateInstantiationPattern();
251 aInfo
.parentRecord
= cxxRecordDecl
;
252 aInfo
.parentClass
= cxxRecordDecl
->getQualifiedNameAsString();
256 aInfo
.parentRecord
= recordDecl
;
257 aInfo
.parentClass
= recordDecl
->getQualifiedNameAsString();
260 aInfo
.fieldName
= fieldDecl
->getNameAsString();
261 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
262 size_t idx
= aInfo
.fieldName
.find(SRCDIR
);
263 if (idx
!= std::string::npos
) {
264 aInfo
.fieldName
= aInfo
.fieldName
.replace(idx
, strlen(SRCDIR
), "");
266 aInfo
.fieldType
= fieldDecl
->getType().getAsString();
268 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( fieldDecl
->getLocation() );
269 StringRef name
= getFilenameOfLocation(expansionLoc
);
270 aInfo
.sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
271 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
273 switch (fieldDecl
->getAccess())
275 case AS_public
: aInfo
.access
= "public"; break;
276 case AS_private
: aInfo
.access
= "private"; break;
277 case AS_protected
: aInfo
.access
= "protected"; break;
278 default: aInfo
.access
= "unknown"; break;
284 bool UnusedFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
286 fieldDecl
= fieldDecl
->getCanonicalDecl();
287 if (ignoreLocation( fieldDecl
->getBeginLoc() )) {
290 // ignore stuff that forms part of the stable URE interface
291 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
295 if (fieldDecl
->getInClassInitializer() && !isSomeKindOfZero(fieldDecl
->getInClassInitializer())) {
296 writeToSet
.insert(niceName(fieldDecl
));
299 definitionSet
.insert(niceName(fieldDecl
));
304 Does the expression being used to initialise a field value evaluate to
305 the same as a default value?
307 bool UnusedFields::isSomeKindOfZero(const Expr
* arg
)
310 arg
= arg
->IgnoreParenCasts();
311 if (isa
<CXXDefaultArgExpr
>(arg
)) {
312 arg
= dyn_cast
<CXXDefaultArgExpr
>(arg
)->getExpr();
314 arg
= arg
->IgnoreParenCasts();
315 // ignore this, it seems to trigger an infinite recursion
316 if (isa
<UnaryExprOrTypeTraitExpr
>(arg
)) {
319 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(arg
)) {
320 return cxxConstructExpr
->getConstructor()->isDefaultConstructor();
323 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
327 if (isa
<CXXNullPtrLiteralExpr
>(arg
)) {
330 if (isa
<MaterializeTemporaryExpr
>(arg
))
332 const CXXBindTemporaryExpr
* strippedArg
= dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
335 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
336 if (temp
->getNumArgs() == 0)
338 if (loplugin::TypeCheck(temp
->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
341 if (loplugin::TypeCheck(temp
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
349 // Get the expression contents.
350 // This helps us find params which are always initialised with something like "OUString()".
351 SourceManager
& SM
= compiler
.getSourceManager();
352 SourceLocation startLoc
= arg
->getBeginLoc();
353 SourceLocation endLoc
= arg
->getEndLoc();
354 const char *p1
= SM
.getCharacterData( startLoc
);
355 const char *p2
= SM
.getCharacterData( endLoc
);
356 if (!p1
|| !p2
|| (p2
- p1
) < 0 || (p2
- p1
) > 40) {
359 unsigned n
= Lexer::MeasureTokenLength( endLoc
, SM
, compiler
.getLangOpts());
360 std::string
s( p1
, p2
- p1
+ n
);
361 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
362 std::replace( s
.begin(), s
.end(), '\r', ' ');
363 std::replace( s
.begin(), s
.end(), '\n', ' ');
364 std::replace( s
.begin(), s
.end(), '\t', ' ');
366 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
367 if (s
== "OUString()")
369 else if (s
== "OString()")
374 static char easytolower(char in
)
376 if (in
<='Z' && in
>='A')
381 bool startswith(const std::string
& rStr
, const char* pSubStr
)
383 return rStr
.compare(0, strlen(pSubStr
), pSubStr
) == 0;
386 bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
388 auto copy
= insideMoveOrCopyOrCloneDeclParent
;
389 if (!ignoreLocation(cxxConstructorDecl
->getBeginLoc()) && cxxConstructorDecl
->isThisDeclarationADefinition())
391 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
392 insideMoveOrCopyOrCloneDeclParent
= cxxConstructorDecl
->getParent();
394 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
395 insideMoveOrCopyOrCloneDeclParent
= copy
;
399 bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
401 auto copy1
= insideMoveOrCopyOrCloneDeclParent
;
402 auto copy2
= insideFunctionDecl
;
403 if (!ignoreLocation(cxxMethodDecl
->getBeginLoc()) && cxxMethodDecl
->isThisDeclarationADefinition())
405 if (cxxMethodDecl
->isCopyAssignmentOperator()
406 || cxxMethodDecl
->isMoveAssignmentOperator()
407 || (cxxMethodDecl
->getIdentifier()
408 && (cxxMethodDecl
->getName().startswith("Clone")
409 || cxxMethodDecl
->getName().startswith("clone")
410 || cxxMethodDecl
->getName().startswith("createClone"))))
411 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
412 // these are similar in that they tend to simply enumerate all the fields of an object without putting
413 // them to some useful purpose
414 auto op
= cxxMethodDecl
->getOverloadedOperator();
415 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
416 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
418 insideFunctionDecl
= cxxMethodDecl
;
419 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
420 insideMoveOrCopyOrCloneDeclParent
= copy1
;
421 insideFunctionDecl
= copy2
;
425 bool UnusedFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
427 auto copy1
= insideStreamOutputOperator
;
428 auto copy2
= insideFunctionDecl
;
429 auto copy3
= insideMoveOrCopyOrCloneDeclParent
;
430 if (functionDecl
->getLocation().isValid() && !ignoreLocation(functionDecl
->getBeginLoc()) && functionDecl
->isThisDeclarationADefinition())
432 auto op
= functionDecl
->getOverloadedOperator();
433 if (op
== OO_LessLess
434 && functionDecl
->getNumParams() == 2)
436 QualType qt
= functionDecl
->getParamDecl(1)->getType();
437 insideStreamOutputOperator
= qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
439 // these are similar in that they tend to simply enumerate all the fields of an object without putting
440 // them to some useful purpose
441 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
443 QualType qt
= functionDecl
->getParamDecl(1)->getType();
444 insideMoveOrCopyOrCloneDeclParent
= qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
447 insideFunctionDecl
= functionDecl
;
448 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
449 insideStreamOutputOperator
= copy1
;
450 insideFunctionDecl
= copy2
;
451 insideMoveOrCopyOrCloneDeclParent
= copy3
;
455 bool UnusedFields::TraverseIfStmt(IfStmt
* ifStmt
)
457 FieldDecl
const * memberFieldDecl
= nullptr;
458 Expr
const * cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
460 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(cond
))
462 if (auto cxxConvert
= dyn_cast_or_null
<CXXConversionDecl
>(memberCallExpr
->getMethodDecl()))
464 if (cxxConvert
->getConversionType()->isBooleanType())
465 if (auto memberExpr
= dyn_cast
<MemberExpr
>(memberCallExpr
->getImplicitObjectArgument()->IgnoreParenImpCasts()))
466 if ((memberFieldDecl
= dyn_cast
<FieldDecl
>(memberExpr
->getMemberDecl())))
467 insideConditionalCheckOfMemberSet
.push_back(memberFieldDecl
);
469 else if (auto cxxMethod
= memberCallExpr
->getMethodDecl())
471 if (cxxMethod
->getIdentifier() && cxxMethod
->getName() == "get" && memberCallExpr
->getNumArgs()==0)
472 if (auto memberExpr
= dyn_cast
<MemberExpr
>(memberCallExpr
->getImplicitObjectArgument()->IgnoreParenImpCasts()))
473 if ((memberFieldDecl
= dyn_cast
<FieldDecl
>(memberExpr
->getMemberDecl())))
474 insideConditionalCheckOfMemberSet
.push_back(memberFieldDecl
);
477 else if (auto memberExpr
= dyn_cast
<MemberExpr
>(cond
))
479 if ((memberFieldDecl
= dyn_cast
<FieldDecl
>(memberExpr
->getMemberDecl())))
480 insideConditionalCheckOfMemberSet
.push_back(memberFieldDecl
);
483 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
485 insideConditionalCheckOfMemberSet
.pop_back();
489 bool UnusedFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
491 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
492 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
496 fieldDecl
= fieldDecl
->getCanonicalDecl();
497 if (ignoreLocation(fieldDecl
->getBeginLoc())) {
500 // ignore stuff that forms part of the stable URE interface
501 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
505 checkTouchedFromOutside(fieldDecl
, memberExpr
);
507 checkIfReadFrom(fieldDecl
, memberExpr
);
509 checkIfWrittenTo(fieldDecl
, memberExpr
);
514 void UnusedFields::checkIfReadFrom(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
516 if (insideMoveOrCopyOrCloneDeclParent
|| insideStreamOutputOperator
)
518 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
519 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
520 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
522 // we don't care about reads when the field is being used in an output operator, this is normally
524 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideStreamOutputOperator
))
528 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
529 const Stmt
* child
= memberExpr
;
530 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
531 // walk up the tree until we find something interesting
532 bool bPotentiallyReadFrom
= false;
534 auto walkUp
= [&]() {
536 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
537 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
543 // check if we're inside a CXXCtorInitializer or a VarDecl
544 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
545 if ( parentsRange
.begin() != parentsRange
.end())
547 const Decl
* decl
= parentsRange
.begin()->get
<Decl
>();
548 if (decl
&& (isa
<CXXConstructorDecl
>(decl
) || isa
<VarDecl
>(decl
)))
549 bPotentiallyReadFrom
= true;
551 if (!bPotentiallyReadFrom
)
555 if (isa
<CXXReinterpretCastExpr
>(parent
))
557 // once we see one of these, there is not much useful we can know
558 bPotentiallyReadFrom
= true;
561 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
562 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
566 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
568 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
569 if (memberExpr
->getType()->isArrayType() && op
== UO_Deref
)
571 // ignore, deref'ing an array does not count as a read
573 else if (op
== UO_AddrOf
|| op
== UO_Deref
574 || op
== UO_Plus
|| op
== UO_Minus
575 || op
== UO_Not
|| op
== UO_LNot
)
577 bPotentiallyReadFrom
= true;
580 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
581 ignore them to find interesting fields that only modified, not usefully read:
582 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
583 But we still walk up in case the result of the expression is used in a read sense.
587 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
589 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
592 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
594 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
597 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
599 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
602 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
604 if (arraySubscriptExpr
->getIdx() == child
)
606 bPotentiallyReadFrom
= true;
611 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
613 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
614 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
615 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
616 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
617 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
618 if (binaryOp
->getLHS() == child
&& assignmentOp
)
622 bPotentiallyReadFrom
= true;
626 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
628 auto op
= operatorCallExpr
->getOperator();
629 const bool assignmentOp
= op
== OO_Equal
|| op
== OO_StarEqual
||
630 op
== OO_SlashEqual
|| op
== OO_PercentEqual
||
631 op
== OO_PlusEqual
|| op
== OO_MinusEqual
||
632 op
== OO_LessLessEqual
||
633 op
== OO_AmpEqual
|| op
== OO_CaretEqual
||
635 if (operatorCallExpr
->getArg(0) == child
&& assignmentOp
)
637 else if (op
== OO_GreaterGreaterEqual
&& operatorCallExpr
->getArg(1) == child
)
638 break; // this is a write-only call
641 bPotentiallyReadFrom
= true;
645 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
647 bool bWriteOnlyCall
= false;
648 const CXXMethodDecl
* callee
= cxxMemberCallExpr
->getMethodDecl();
651 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
652 if (tmp
->isBoundMemberFunction(compiler
.getASTContext())) {
653 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
655 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
657 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
658 // which we could scatter around.
659 std::string name
= callee
->getNameAsString();
660 std::transform(name
.begin(), name
.end(), name
.begin(), easytolower
);
661 if (startswith(name
, "emplace") || name
== "insert"
662 || name
== "erase" || name
== "remove" || name
== "remove_if" || name
== "sort"
663 || name
== "push_back" || name
== "pop_back"
664 || name
== "push_front" || name
== "pop_front"
665 || name
== "reserve" || name
== "resize" || name
== "reset"
666 || name
== "clear" || name
== "fill")
667 // write-only modifications to collections
668 bWriteOnlyCall
= true;
669 else if (name
== "dispose" || name
== "disposeAndClear" || name
== "swap")
670 // we're abusing the write-only analysis here to look for fields which don't have anything useful
671 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
672 // and VclPtr::disposeAndClear
673 bWriteOnlyCall
= true;
677 bPotentiallyReadFrom
= true;
680 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
682 bool bWriteOnlyCall
= false;
683 // check for calls to ReadXXX(foo) type methods, where foo is write-only
684 auto callee
= getCallee(callExpr
);
687 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
688 // which we could scatter around.
689 std::string name
= callee
->getNameAsString();
690 std::transform(name
.begin(), name
.end(), name
.begin(), easytolower
);
691 if (startswith(name
, "read"))
692 // this is a write-only call
693 bWriteOnlyCall
= true;
696 bPotentiallyReadFrom
= true;
699 else if (isa
<ReturnStmt
>(parent
)
700 || isa
<CXXConstructExpr
>(parent
)
701 || isa
<ConditionalOperator
>(parent
)
702 || isa
<SwitchStmt
>(parent
)
703 || isa
<DeclStmt
>(parent
)
704 || isa
<WhileStmt
>(parent
)
705 || isa
<CXXNewExpr
>(parent
)
706 || isa
<ForStmt
>(parent
)
707 || isa
<InitListExpr
>(parent
)
708 || isa
<CXXDependentScopeMemberExpr
>(parent
)
709 || isa
<UnresolvedMemberExpr
>(parent
)
710 || isa
<MaterializeTemporaryExpr
>(parent
))
712 bPotentiallyReadFrom
= true;
715 else if (isa
<CXXDeleteExpr
>(parent
)
716 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
717 || isa
<CXXUnresolvedConstructExpr
>(parent
)
718 || isa
<CompoundStmt
>(parent
)
719 || isa
<LabelStmt
>(parent
)
720 || isa
<CXXForRangeStmt
>(parent
)
721 || isa
<CXXTypeidExpr
>(parent
)
722 || isa
<DefaultStmt
>(parent
))
728 bPotentiallyReadFrom
= true;
737 DiagnosticsEngine::Warning
,
738 "oh dear, what can the matter be?",
739 memberExpr
->getBeginLoc())
740 << memberExpr
->getSourceRange();
742 DiagnosticsEngine::Note
,
744 parent
->getBeginLoc())
745 << parent
->getSourceRange();
750 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
751 if (bPotentiallyReadFrom
)
753 readFromSet
.insert(fieldInfo
);
757 void UnusedFields::checkIfWrittenTo(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
759 if (insideMoveOrCopyOrCloneDeclParent
)
761 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
762 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
763 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
769 // if we're inside a block that looks like
772 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
773 if (std::find(insideConditionalCheckOfMemberSet
.begin(), insideConditionalCheckOfMemberSet
.end(), fieldDecl
) != insideConditionalCheckOfMemberSet
.end())
776 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
777 const Stmt
* child
= memberExpr
;
778 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
779 // walk up the tree until we find something interesting
780 bool bPotentiallyWrittenTo
= false;
782 auto walkUp
= [&]() {
784 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
785 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
791 // check if we have an expression like
793 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
794 if (parentsRange
.begin() != parentsRange
.end())
796 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
797 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
798 // which is of type 'T&&' and also an l-value-ref ?
799 if (varDecl
&& !varDecl
->isImplicit() && loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
801 bPotentiallyWrittenTo
= true;
806 if (isa
<CXXReinterpretCastExpr
>(parent
))
808 // once we see one of these, there is not much useful we can know
809 bPotentiallyWrittenTo
= true;
812 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
813 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
817 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
819 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
820 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
|| op
== UO_PreDec
)
822 bPotentiallyWrittenTo
= true;
826 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
828 if (arraySubscriptExpr
->getIdx() == child
)
832 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
834 auto callee
= getCallee(operatorCallExpr
);
837 // if calling a non-const operator on the field
838 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
839 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
)
841 if (!calleeMethodDecl
->isConst())
842 bPotentiallyWrittenTo
= checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
844 else if (IsPassedByNonConst(fieldDecl
, child
, operatorCallExpr
, *callee
))
846 bPotentiallyWrittenTo
= true;
850 bPotentiallyWrittenTo
= true; // conservative, could improve
853 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
855 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
856 if (calleeMethodDecl
)
858 // if calling a non-const method on the field
859 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
860 if (tmp
->isBoundMemberFunction(compiler
.getASTContext())) {
861 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
863 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
865 if (!calleeMethodDecl
->isConst())
866 bPotentiallyWrittenTo
= checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
869 else if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
, CalleeWrapper(calleeMethodDecl
)))
870 bPotentiallyWrittenTo
= true;
873 bPotentiallyWrittenTo
= true; // can happen in templates
876 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
878 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
, CalleeWrapper(cxxConstructExpr
)))
879 bPotentiallyWrittenTo
= true;
882 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
884 auto callee
= getCallee(callExpr
);
886 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
887 bPotentiallyWrittenTo
= true;
889 bPotentiallyWrittenTo
= true; // conservative, could improve
892 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
894 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
895 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
896 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
897 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
898 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
901 if (binaryOp
->getLHS() == child
)
902 bPotentiallyWrittenTo
= true;
903 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType()).LvalueReference().NonConst())
904 // if the LHS is a non-const reference, we could write to the field later on
905 bPotentiallyWrittenTo
= true;
909 else if (isa
<ReturnStmt
>(parent
))
911 if (insideFunctionDecl
)
913 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
914 if (tc
.LvalueReference().NonConst())
915 bPotentiallyWrittenTo
= true;
919 else if (isa
<ConditionalOperator
>(parent
)
920 || isa
<SwitchStmt
>(parent
)
921 || isa
<DeclStmt
>(parent
)
922 || isa
<WhileStmt
>(parent
)
923 || isa
<CXXNewExpr
>(parent
)
924 || isa
<ForStmt
>(parent
)
925 || isa
<InitListExpr
>(parent
)
926 || isa
<CXXDependentScopeMemberExpr
>(parent
)
927 || isa
<UnresolvedMemberExpr
>(parent
)
928 || isa
<MaterializeTemporaryExpr
>(parent
)
929 || isa
<IfStmt
>(parent
)
930 || isa
<DoStmt
>(parent
)
931 || isa
<CXXDeleteExpr
>(parent
)
932 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
933 || isa
<CXXUnresolvedConstructExpr
>(parent
)
934 || isa
<CompoundStmt
>(parent
)
935 || isa
<LabelStmt
>(parent
)
936 || isa
<CXXForRangeStmt
>(parent
)
937 || isa
<CXXTypeidExpr
>(parent
)
938 || isa
<DefaultStmt
>(parent
))
944 bPotentiallyWrittenTo
= true;
953 DiagnosticsEngine::Warning
,
954 "oh dear, what can the matter be? writtenTo=%0",
955 memberExpr
->getBeginLoc())
956 << bPotentiallyWrittenTo
957 << memberExpr
->getSourceRange();
961 DiagnosticsEngine::Note
,
963 parent
->getBeginLoc())
964 << parent
->getSourceRange();
968 fieldDecl
->getType()->dump();
971 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
972 if (bPotentiallyWrittenTo
)
974 writeToSet
.insert(fieldInfo
);
978 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
979 bool UnusedFields::checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
)
981 auto const tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
982 bool listLike
= false, setLike
= false, mapLike
= false, cssSequence
= false;
983 if (tc
.Class("deque").StdNamespace()
984 || tc
.Class("list").StdNamespace()
985 || tc
.Class("queue").StdNamespace()
986 || tc
.Class("vector").StdNamespace())
990 else if (tc
.Class("set").StdNamespace()
991 || tc
.Class("unordered_set").StdNamespace())
995 else if (tc
.Class("map").StdNamespace()
996 || tc
.Class("unordered_map").StdNamespace())
1000 else if (tc
.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
1007 if (calleeMethodDecl
->isOverloadedOperator())
1009 auto oo
= calleeMethodDecl
->getOverloadedOperator();
1012 // This is operator[]. We only care about things that add elements to the collection.
1013 // if nothing modifies the size of the collection, then nothing useful
1020 auto name
= calleeMethodDecl
->getName();
1021 if (listLike
|| setLike
|| mapLike
)
1023 if (name
== "reserve" || name
== "shrink_to_fit" || name
== "clear"
1024 || name
== "erase" || name
== "pop_back" || name
== "pop_front"
1025 || name
== "front" || name
== "back" || name
== "data"
1026 || name
== "remove" || name
== "remove_if"
1027 || name
== "unique" || name
== "sort"
1028 || name
== "begin" || name
== "end"
1029 || name
== "rbegin" || name
== "rend"
1030 || name
== "at" || name
== "find" || name
== "equal_range"
1031 || name
== "lower_bound" || name
== "upper_bound")
1036 if (name
== "getArray" || name
== "begin" || name
== "end")
1043 bool UnusedFields::IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
1044 CalleeWrapper calleeFunctionDecl
)
1046 unsigned len
= std::min(callExpr
.getNumArgs(),
1047 calleeFunctionDecl
.getNumParams());
1048 // if it's an array, passing it by value to a method typically means the
1049 // callee takes a pointer and can modify the array
1050 if (fieldDecl
->getType()->isConstantArrayType())
1052 for (unsigned i
= 0; i
< len
; ++i
)
1053 if (callExpr
.getArg(i
) == child
)
1054 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
1059 for (unsigned i
= 0; i
< len
; ++i
)
1060 if (callExpr
.getArg(i
) == child
)
1061 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).LvalueReference().NonConst())
1067 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
1068 // have to do it here
1069 bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl
* cxxConstructorDecl
)
1071 if (ignoreLocation( cxxConstructorDecl
->getBeginLoc() )) {
1074 // ignore stuff that forms part of the stable URE interface
1075 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(cxxConstructorDecl
->getLocation()))) {
1079 // templates make EvaluateAsInt crash inside clang
1080 if (cxxConstructorDecl
->isDependentContext())
1083 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
1084 if (insideMoveOrCopyOrCloneDeclParent
&& cxxConstructorDecl
->getParent() == insideMoveOrCopyOrCloneDeclParent
)
1087 for(auto it
= cxxConstructorDecl
->init_begin(); it
!= cxxConstructorDecl
->init_end(); ++it
)
1089 const CXXCtorInitializer
* init
= *it
;
1090 const FieldDecl
* fieldDecl
= init
->getMember();
1091 if (fieldDecl
&& init
->getInit() && !isSomeKindOfZero(init
->getInit()))
1093 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
1094 writeToSet
.insert(fieldInfo
);
1100 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
1101 // have to do it here.
1102 bool UnusedFields::VisitInitListExpr( const InitListExpr
* initListExpr
)
1104 if (ignoreLocation( initListExpr
->getBeginLoc() ))
1107 QualType varType
= initListExpr
->getType().getDesugaredType(compiler
.getASTContext());
1108 auto recordType
= varType
->getAs
<RecordType
>();
1112 auto recordDecl
= recordType
->getDecl();
1113 for (auto it
= recordDecl
->field_begin(); it
!= recordDecl
->field_end(); ++it
)
1115 MyFieldInfo fieldInfo
= niceName(*it
);
1116 writeToSet
.insert(fieldInfo
);
1122 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
1124 const Decl
* decl
= declRefExpr
->getDecl();
1125 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
1129 fieldDecl
= fieldDecl
->getCanonicalDecl();
1130 if (ignoreLocation(fieldDecl
->getBeginLoc())) {
1133 // ignore stuff that forms part of the stable URE interface
1134 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
1137 checkTouchedFromOutside(fieldDecl
, declRefExpr
);
1141 void UnusedFields::checkTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
) {
1142 const FunctionDecl
* memberExprParentFunction
= getParentFunctionDecl(memberExpr
);
1143 const CXXMethodDecl
* methodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberExprParentFunction
);
1145 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
1147 // it's touched from somewhere outside a class
1149 touchedFromOutsideSet
.insert(fieldInfo
);
1153 auto constructorDecl
= dyn_cast
<CXXConstructorDecl
>(methodDecl
);
1154 if (methodDecl
->isCopyAssignmentOperator() || methodDecl
->isMoveAssignmentOperator()) {
1155 // ignore move/copy operator, it's self->self
1156 } else if (constructorDecl
&& (constructorDecl
->isCopyConstructor() || constructorDecl
->isMoveConstructor())) {
1157 // ignore move/copy constructor, it's self->self
1159 if (memberExprParentFunction
->getParent() == fieldDecl
->getParent()) {
1160 touchedFromInsideSet
.insert(fieldInfo
);
1161 if (!constructorDecl
)
1162 touchedFromOutsideConstructorSet
.insert(fieldInfo
);
1164 if (fieldDecl
->getName() == "m_pShell")
1166 if (memberExprParentFunction
)
1167 memberExprParentFunction
->dump();
1169 std::cout
<< "site2" << std::endl
;
1171 touchedFromOutsideSet
.insert(fieldInfo
);
1176 compat::optional
<CalleeWrapper
> UnusedFields::getCallee(CallExpr
const * callExpr
)
1178 FunctionDecl
const * functionDecl
= callExpr
->getDirectCallee();
1180 return CalleeWrapper(functionDecl
);
1182 // Extract the functionprototype from a type
1183 clang::Type
const * calleeType
= callExpr
->getCallee()->getType().getTypePtr();
1184 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>()) {
1185 if (auto prototype
= pointerType
->getPointeeType()->getUnqualifiedDesugaredType()->getAs
<FunctionProtoType
>()) {
1186 return CalleeWrapper(prototype
);
1190 return compat::optional
<CalleeWrapper
>();
1193 loplugin::Plugin::Registration
< UnusedFields
> X("unusedfields", false);
1199 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */