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>
26 This performs two analyses:
27 (1) look for unused fields
28 (2) look for fields that are write-only
30 We dmp a list of calls to methods, and a list of field definitions.
31 Then we will post-process the 2 lists and find the set of unused methods.
33 Be warned that it produces around 5G of log file.
35 The process goes something like this:
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedfields' check
38 $ ./compilerplugins/clang/unusedfields.py
41 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $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
53 const RecordDecl
* parentRecord
;
54 std::string parentClass
;
55 std::string fieldName
;
56 std::string fieldType
;
57 std::string sourceLocation
;
60 bool operator < (const MyFieldInfo
&lhs
, const MyFieldInfo
&rhs
)
62 return std::tie(lhs
.parentClass
, lhs
.fieldName
)
63 < std::tie(rhs
.parentClass
, rhs
.fieldName
);
67 // try to limit the voluminous output a little
68 static std::set
<MyFieldInfo
> touchedFromInsideSet
;
69 static std::set
<MyFieldInfo
> touchedFromOutsideSet
;
70 static std::set
<MyFieldInfo
> touchedFromOutsideConstructorSet
;
71 static std::set
<MyFieldInfo
> readFromSet
;
72 static std::set
<MyFieldInfo
> writeToSet
;
73 static std::set
<MyFieldInfo
> definitionSet
;
76 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
80 const CallExpr
* m_callExpr
;
81 const CXXConstructExpr
* m_cxxConstructExpr
;
83 CallerWrapper(const CallExpr
* callExpr
) : m_callExpr(callExpr
), m_cxxConstructExpr(nullptr) {}
84 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
) : m_callExpr(nullptr), m_cxxConstructExpr(cxxConstructExpr
) {}
85 unsigned getNumArgs () const
86 { return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs(); }
87 const Expr
* getArg (unsigned i
) const
88 { return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
); }
92 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
93 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
94 const FunctionProtoType
* m_functionPrototype
= nullptr;
96 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
) : m_calleeFunctionDecl(calleeFunctionDecl
) {}
97 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
) : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor()) {}
98 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
) : m_functionPrototype(functionPrototype
) {}
99 unsigned getNumParams() const
101 if (m_calleeFunctionDecl
)
102 return m_calleeFunctionDecl
->getNumParams();
103 else if (m_cxxConstructorDecl
)
104 return m_cxxConstructorDecl
->getNumParams();
105 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
106 // FunctionProtoType will assert if we call getParamTypes() and it has no params
109 return m_functionPrototype
->getParamTypes().size();
111 const QualType
getParamType(unsigned i
) const
113 if (m_calleeFunctionDecl
)
114 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
115 else if (m_cxxConstructorDecl
)
116 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
118 return m_functionPrototype
->getParamTypes()[i
];
120 std::string
getNameAsString() const
122 if (m_calleeFunctionDecl
)
123 return m_calleeFunctionDecl
->getNameAsString();
124 else if (m_cxxConstructorDecl
)
125 return m_cxxConstructorDecl
->getNameAsString();
129 CXXMethodDecl
const * getAsCXXMethodDecl() const
131 if (m_calleeFunctionDecl
)
132 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
138 public RecursiveASTVisitor
<UnusedFields
>, public loplugin::Plugin
141 explicit UnusedFields(loplugin::InstantiationData
const & data
):
144 virtual void run() override
;
146 bool shouldVisitTemplateInstantiations () const { return true; }
147 bool shouldVisitImplicitCode() const { return true; }
149 bool VisitFieldDecl( const FieldDecl
* );
150 bool VisitMemberExpr( const MemberExpr
* );
151 bool VisitDeclRefExpr( const DeclRefExpr
* );
152 bool VisitCXXConstructorDecl( const CXXConstructorDecl
* );
153 bool VisitInitListExpr( const InitListExpr
* );
154 bool TraverseCXXConstructorDecl( CXXConstructorDecl
* );
155 bool TraverseCXXMethodDecl( CXXMethodDecl
* );
156 bool TraverseFunctionDecl( FunctionDecl
* );
157 bool TraverseIfStmt( IfStmt
* );
160 MyFieldInfo
niceName(const FieldDecl
*);
161 void checkTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
162 void checkWriteOnly(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
163 void checkReadOnly(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
164 bool isSomeKindOfZero(const Expr
* arg
);
165 bool IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
166 CalleeWrapper calleeFunctionDecl
);
167 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const *);
169 RecordDecl
* insideMoveOrCopyOrCloneDeclParent
= nullptr;
170 RecordDecl
* insideStreamOutputOperator
= nullptr;
171 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
172 // we store the parent function on the way down the AST.
173 FunctionDecl
* insideFunctionDecl
= nullptr;
174 std::vector
<FieldDecl
const *> insideConditionalCheckOfMemberSet
;
177 void UnusedFields::run()
179 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
181 if (!isUnitTestMode())
183 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
184 // writing to the same logfile
186 for (const MyFieldInfo
& s
: touchedFromInsideSet
)
187 output
+= "inside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
188 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
189 output
+= "outside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
190 for (const MyFieldInfo
& s
: touchedFromOutsideConstructorSet
)
191 output
+= "outside-constructor:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
192 for (const MyFieldInfo
& s
: readFromSet
)
193 output
+= "read:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
194 for (const MyFieldInfo
& s
: writeToSet
)
195 output
+= "write:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
196 for (const MyFieldInfo
& s
: definitionSet
)
197 output
+= "definition:\t" + s
.access
+ "\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.fieldType
+ "\t" + s
.sourceLocation
+ "\n";
198 std::ofstream myfile
;
199 myfile
.open( WORKDIR
"/loplugin.unusedfields.log", std::ios::app
| std::ios::out
);
205 for (const MyFieldInfo
& s
: readFromSet
)
207 DiagnosticsEngine::Warning
,
209 s
.parentRecord
->getLocStart())
211 for (const MyFieldInfo
& s
: writeToSet
)
213 DiagnosticsEngine::Warning
,
215 s
.parentRecord
->getLocStart())
221 MyFieldInfo
UnusedFields::niceName(const FieldDecl
* fieldDecl
)
225 const RecordDecl
* recordDecl
= fieldDecl
->getParent();
227 if (const CXXRecordDecl
* cxxRecordDecl
= dyn_cast
<CXXRecordDecl
>(recordDecl
))
229 if (cxxRecordDecl
->getTemplateInstantiationPattern())
230 cxxRecordDecl
= cxxRecordDecl
->getTemplateInstantiationPattern();
231 aInfo
.parentRecord
= cxxRecordDecl
;
232 aInfo
.parentClass
= cxxRecordDecl
->getQualifiedNameAsString();
236 aInfo
.parentRecord
= recordDecl
;
237 aInfo
.parentClass
= recordDecl
->getQualifiedNameAsString();
240 aInfo
.fieldName
= fieldDecl
->getNameAsString();
241 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
242 size_t idx
= aInfo
.fieldName
.find(SRCDIR
);
243 if (idx
!= std::string::npos
) {
244 aInfo
.fieldName
= aInfo
.fieldName
.replace(idx
, strlen(SRCDIR
), "");
246 aInfo
.fieldType
= fieldDecl
->getType().getAsString();
248 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( fieldDecl
->getLocation() );
249 StringRef name
= compiler
.getSourceManager().getFilename(expansionLoc
);
250 aInfo
.sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
251 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
253 switch (fieldDecl
->getAccess())
255 case AS_public
: aInfo
.access
= "public"; break;
256 case AS_private
: aInfo
.access
= "private"; break;
257 case AS_protected
: aInfo
.access
= "protected"; break;
258 default: aInfo
.access
= "unknown"; break;
264 bool UnusedFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
266 fieldDecl
= fieldDecl
->getCanonicalDecl();
267 if (ignoreLocation( fieldDecl
)) {
270 // ignore stuff that forms part of the stable URE interface
271 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
275 if (fieldDecl
->getInClassInitializer() && !isSomeKindOfZero(fieldDecl
->getInClassInitializer())) {
276 writeToSet
.insert(niceName(fieldDecl
));
279 definitionSet
.insert(niceName(fieldDecl
));
284 Does the expression being used to initialise a field value evaluate to
285 the same as a default value?
287 bool UnusedFields::isSomeKindOfZero(const Expr
* arg
)
290 arg
= arg
->IgnoreParenCasts();
291 if (isa
<CXXDefaultArgExpr
>(arg
)) {
292 arg
= dyn_cast
<CXXDefaultArgExpr
>(arg
)->getExpr();
294 arg
= arg
->IgnoreParenCasts();
295 // ignore this, it seems to trigger an infinite recursion
296 if (isa
<UnaryExprOrTypeTraitExpr
>(arg
)) {
299 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(arg
)) {
300 return cxxConstructExpr
->getConstructor()->isDefaultConstructor();
303 if (arg
->EvaluateAsInt(x1
, compiler
.getASTContext()))
307 if (isa
<CXXNullPtrLiteralExpr
>(arg
)) {
310 if (isa
<MaterializeTemporaryExpr
>(arg
))
312 const CXXBindTemporaryExpr
* strippedArg
= dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
315 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
316 if (temp
->getNumArgs() == 0)
318 if (loplugin::TypeCheck(temp
->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
321 if (loplugin::TypeCheck(temp
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
329 // Get the expression contents.
330 // This helps us find params which are always initialised with something like "OUString()".
331 SourceManager
& SM
= compiler
.getSourceManager();
332 SourceLocation startLoc
= arg
->getLocStart();
333 SourceLocation endLoc
= arg
->getLocEnd();
334 const char *p1
= SM
.getCharacterData( startLoc
);
335 const char *p2
= SM
.getCharacterData( endLoc
);
336 if (!p1
|| !p2
|| (p2
- p1
) < 0 || (p2
- p1
) > 40) {
339 unsigned n
= Lexer::MeasureTokenLength( endLoc
, SM
, compiler
.getLangOpts());
340 std::string
s( p1
, p2
- p1
+ n
);
341 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
342 std::replace( s
.begin(), s
.end(), '\r', ' ');
343 std::replace( s
.begin(), s
.end(), '\n', ' ');
344 std::replace( s
.begin(), s
.end(), '\t', ' ');
346 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
347 if (s
== "OUString()")
349 else if (s
== "OString()")
351 else if (s
== "aEmptyOUStr") //sw
353 else if (s
== "EMPTY_OUSTRING")//sc
355 else if (s
== "GetEmptyOUString()") //sc
360 static char easytolower(char in
)
362 if (in
<='Z' && in
>='A')
367 bool startswith(const std::string
& rStr
, const char* pSubStr
)
369 return rStr
.compare(0, strlen(pSubStr
), pSubStr
) == 0;
372 bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
374 auto copy
= insideMoveOrCopyOrCloneDeclParent
;
375 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
377 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
378 insideMoveOrCopyOrCloneDeclParent
= cxxConstructorDecl
->getParent();
380 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
381 insideMoveOrCopyOrCloneDeclParent
= copy
;
385 bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
387 auto copy1
= insideMoveOrCopyOrCloneDeclParent
;
388 auto copy2
= insideFunctionDecl
;
389 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
391 if (cxxMethodDecl
->isCopyAssignmentOperator()
392 || cxxMethodDecl
->isMoveAssignmentOperator()
393 || (cxxMethodDecl
->getIdentifier() && cxxMethodDecl
->getName() == "Clone"))
394 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
396 insideFunctionDecl
= cxxMethodDecl
;
397 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
398 insideMoveOrCopyOrCloneDeclParent
= copy1
;
399 insideFunctionDecl
= copy2
;
403 bool UnusedFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
405 auto copy1
= insideStreamOutputOperator
;
406 auto copy2
= insideFunctionDecl
;
407 if (functionDecl
->getLocation().isValid() && !ignoreLocation(functionDecl
) && functionDecl
->isThisDeclarationADefinition())
409 if (functionDecl
->getOverloadedOperator() == OO_LessLess
410 && functionDecl
->getNumParams() == 2)
412 QualType qt
= functionDecl
->getParamDecl(1)->getType();
413 insideStreamOutputOperator
= qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
416 insideFunctionDecl
= functionDecl
;
417 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
418 insideStreamOutputOperator
= copy1
;
419 insideFunctionDecl
= copy2
;
423 bool UnusedFields::TraverseIfStmt(IfStmt
* ifStmt
)
425 FieldDecl
const * memberFieldDecl
= nullptr;
426 Expr
const * cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
427 if (auto memberExpr
= dyn_cast
<MemberExpr
>(cond
))
429 if ((memberFieldDecl
= dyn_cast
<FieldDecl
>(memberExpr
->getMemberDecl())))
430 insideConditionalCheckOfMemberSet
.push_back(memberFieldDecl
);
432 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
434 insideConditionalCheckOfMemberSet
.pop_back();
438 bool UnusedFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
440 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
441 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
445 fieldDecl
= fieldDecl
->getCanonicalDecl();
446 if (ignoreLocation(fieldDecl
)) {
449 // ignore stuff that forms part of the stable URE interface
450 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
454 checkTouchedFromOutside(fieldDecl
, memberExpr
);
456 checkWriteOnly(fieldDecl
, memberExpr
);
458 checkReadOnly(fieldDecl
, memberExpr
);
463 void UnusedFields::checkWriteOnly(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
465 if (insideMoveOrCopyOrCloneDeclParent
|| insideStreamOutputOperator
)
467 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
468 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
469 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
471 // we don't care about reads when the field is being used in an output operator, this is normally
473 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideStreamOutputOperator
))
477 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
478 const Stmt
* child
= memberExpr
;
479 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
480 // walk up the tree until we find something interesting
481 bool bPotentiallyReadFrom
= false;
483 auto walkupUp
= [&]() {
485 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
486 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
492 // check if we're inside a CXXCtorInitializer or a VarDecl
493 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
494 if ( parentsRange
.begin() != parentsRange
.end())
496 const Decl
* decl
= parentsRange
.begin()->get
<Decl
>();
497 if (decl
&& (isa
<CXXConstructorDecl
>(decl
) || isa
<VarDecl
>(decl
)))
498 bPotentiallyReadFrom
= true;
500 if (!bPotentiallyReadFrom
)
504 if (isa
<CXXReinterpretCastExpr
>(parent
))
506 // once we see one of these, there is not much useful we can know
507 bPotentiallyReadFrom
= true;
510 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
511 #if CLANG_VERSION >= 40000
512 || isa
<ArrayInitLoopExpr
>(parent
)
514 || isa
<ExprWithCleanups
>(parent
))
518 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
520 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
521 if (memberExpr
->getType()->isArrayType() && op
== UO_Deref
)
523 // ignore, deref'ing an array does not count as a read
525 else if (op
== UO_AddrOf
|| op
== UO_Deref
526 || op
== UO_Plus
|| op
== UO_Minus
527 || op
== UO_Not
|| op
== UO_LNot
528 || op
== UO_PreInc
|| op
== UO_PostInc
529 || op
== UO_PreDec
|| op
== UO_PostDec
)
531 bPotentiallyReadFrom
= true;
536 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
538 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
541 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
543 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
546 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
548 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
551 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
553 if (arraySubscriptExpr
->getIdx() == child
)
555 bPotentiallyReadFrom
= true;
560 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
562 // check for calls to ReadXXX() type methods and the operator>>= methods on Any.
563 auto callee
= getCallee(callExpr
);
566 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
567 // which we could scatter around.
568 std::string name
= callee
->getNameAsString();
569 std::transform(name
.begin(), name
.end(), name
.begin(), easytolower
);
570 if (startswith(name
, "read"))
571 // this is a write-only call
573 else if (name
.find(">>=") != std::string::npos
&& callExpr
->getArg(1) == child
)
574 // this is a write-only call
576 else if (name
== "clear" || name
== "dispose" || name
== "disposeAndClear" || name
== "swap")
577 // we're abusing the write-only analysis here to look for fields which don't have anything useful
578 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
579 // and VclPtr::disposeAndClear
582 bPotentiallyReadFrom
= true;
585 bPotentiallyReadFrom
= true;
588 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
590 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
591 // If the child is on the LHS and it is an assignment op, we are obviously not reading from it
592 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
593 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
594 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
595 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
596 if (!(binaryOp
->getLHS() == child
&& assignmentOp
)) {
597 bPotentiallyReadFrom
= true;
601 else if (isa
<ReturnStmt
>(parent
)
602 || isa
<CXXConstructExpr
>(parent
)
603 || isa
<ConditionalOperator
>(parent
)
604 || isa
<SwitchStmt
>(parent
)
605 || isa
<DeclStmt
>(parent
)
606 || isa
<WhileStmt
>(parent
)
607 || isa
<CXXNewExpr
>(parent
)
608 || isa
<ForStmt
>(parent
)
609 || isa
<InitListExpr
>(parent
)
610 || isa
<CXXDependentScopeMemberExpr
>(parent
)
611 || isa
<UnresolvedMemberExpr
>(parent
)
612 || isa
<MaterializeTemporaryExpr
>(parent
))
614 bPotentiallyReadFrom
= true;
617 else if (isa
<CXXDeleteExpr
>(parent
)
618 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
619 || isa
<CXXUnresolvedConstructExpr
>(parent
)
620 || isa
<CompoundStmt
>(parent
)
621 || isa
<LabelStmt
>(parent
)
622 || isa
<CXXForRangeStmt
>(parent
)
623 || isa
<CXXTypeidExpr
>(parent
)
624 || isa
<DefaultStmt
>(parent
))
630 bPotentiallyReadFrom
= true;
639 DiagnosticsEngine::Warning
,
640 "oh dear, what can the matter be?",
641 memberExpr
->getLocStart())
642 << memberExpr
->getSourceRange();
644 DiagnosticsEngine::Note
,
646 parent
->getLocStart())
647 << parent
->getSourceRange();
652 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
653 if (bPotentiallyReadFrom
)
654 readFromSet
.insert(fieldInfo
);
657 void UnusedFields::checkReadOnly(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
659 if (insideMoveOrCopyOrCloneDeclParent
)
661 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
662 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
663 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
667 // if we're inside a block that looks like
670 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
671 if (std::find(insideConditionalCheckOfMemberSet
.begin(), insideConditionalCheckOfMemberSet
.end(), fieldDecl
) != insideConditionalCheckOfMemberSet
.end())
674 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
675 const Stmt
* child
= memberExpr
;
676 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
677 // walk up the tree until we find something interesting
678 bool bPotentiallyWrittenTo
= false;
680 auto walkupUp
= [&]() {
682 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
683 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
689 // check if we have an expression like
691 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
692 if (parentsRange
.begin() != parentsRange
.end())
694 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
695 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
696 // which is of type 'T&&' and also an l-value-ref ?
697 if (varDecl
&& !varDecl
->isImplicit() && loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
699 bPotentiallyWrittenTo
= true;
704 if (isa
<CXXReinterpretCastExpr
>(parent
))
706 // once we see one of these, there is not much useful we can know
707 bPotentiallyWrittenTo
= true;
710 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
711 #if CLANG_VERSION >= 40000
712 || isa
<ArrayInitLoopExpr
>(parent
)
714 || isa
<ExprWithCleanups
>(parent
))
718 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
720 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
721 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
|| op
== UO_PreDec
)
723 bPotentiallyWrittenTo
= true;
727 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
729 if (arraySubscriptExpr
->getIdx() == child
)
733 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
735 auto callee
= getCallee(operatorCallExpr
);
738 // if calling a non-const operator on the field
739 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
741 && operatorCallExpr
->getArg(0) == child
&& !calleeMethodDecl
->isConst())
743 bPotentiallyWrittenTo
= true;
745 else if (IsPassedByNonConst(fieldDecl
, child
, operatorCallExpr
, *callee
))
747 bPotentiallyWrittenTo
= true;
751 bPotentiallyWrittenTo
= true; // conservative, could improve
754 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
756 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
757 if (calleeMethodDecl
)
759 // if calling a non-const method on the field
760 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
761 if (tmp
->isBoundMemberFunction(compiler
.getASTContext())) {
762 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
764 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
765 && !calleeMethodDecl
->isConst())
767 bPotentiallyWrittenTo
= true;
770 if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
, CalleeWrapper(calleeMethodDecl
)))
771 bPotentiallyWrittenTo
= true;
774 bPotentiallyWrittenTo
= true; // can happen in templates
777 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
779 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
, CalleeWrapper(cxxConstructExpr
)))
780 bPotentiallyWrittenTo
= true;
783 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
785 auto callee
= getCallee(callExpr
);
787 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
788 bPotentiallyWrittenTo
= true;
790 bPotentiallyWrittenTo
= true; // conservative, could improve
793 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
795 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
796 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
797 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
798 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
799 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
802 if (binaryOp
->getLHS() == child
)
803 bPotentiallyWrittenTo
= true;
804 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType()).LvalueReference().NonConst())
805 // if the LHS is a non-const reference, we could write to the field later on
806 bPotentiallyWrittenTo
= true;
810 else if (isa
<ReturnStmt
>(parent
))
812 if (insideFunctionDecl
)
814 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
815 if (tc
.LvalueReference().NonConst())
816 bPotentiallyWrittenTo
= true;
820 else if (isa
<ConditionalOperator
>(parent
)
821 || isa
<SwitchStmt
>(parent
)
822 || isa
<DeclStmt
>(parent
)
823 || isa
<WhileStmt
>(parent
)
824 || isa
<CXXNewExpr
>(parent
)
825 || isa
<ForStmt
>(parent
)
826 || isa
<InitListExpr
>(parent
)
827 || isa
<CXXDependentScopeMemberExpr
>(parent
)
828 || isa
<UnresolvedMemberExpr
>(parent
)
829 || isa
<MaterializeTemporaryExpr
>(parent
)
830 || isa
<IfStmt
>(parent
)
831 || isa
<DoStmt
>(parent
)
832 || isa
<CXXDeleteExpr
>(parent
)
833 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
834 || isa
<CXXUnresolvedConstructExpr
>(parent
)
835 || isa
<CompoundStmt
>(parent
)
836 || isa
<LabelStmt
>(parent
)
837 || isa
<CXXForRangeStmt
>(parent
)
838 || isa
<CXXTypeidExpr
>(parent
)
839 || isa
<DefaultStmt
>(parent
))
845 bPotentiallyWrittenTo
= true;
854 DiagnosticsEngine::Warning
,
855 "oh dear, what can the matter be? writtenTo=%0",
856 memberExpr
->getLocStart())
857 << bPotentiallyWrittenTo
858 << memberExpr
->getSourceRange();
862 DiagnosticsEngine::Note
,
864 parent
->getLocStart())
865 << parent
->getSourceRange();
869 fieldDecl
->getType()->dump();
872 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
873 if (bPotentiallyWrittenTo
)
874 writeToSet
.insert(fieldInfo
);
877 bool UnusedFields::IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
878 CalleeWrapper calleeFunctionDecl
)
880 unsigned len
= std::min(callExpr
.getNumArgs(),
881 calleeFunctionDecl
.getNumParams());
882 // if it's an array, passing it by value to a method typically means the
883 // callee takes a pointer and can modify the array
884 if (fieldDecl
->getType()->isConstantArrayType())
886 for (unsigned i
= 0; i
< len
; ++i
)
887 if (callExpr
.getArg(i
) == child
)
888 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
893 for (unsigned i
= 0; i
< len
; ++i
)
894 if (callExpr
.getArg(i
) == child
)
895 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).LvalueReference().NonConst())
901 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
902 // have to do it here
903 bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl
* cxxConstructorDecl
)
905 if (ignoreLocation( cxxConstructorDecl
)) {
908 // ignore stuff that forms part of the stable URE interface
909 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(cxxConstructorDecl
->getLocation()))) {
913 // templates make EvaluateAsInt crash inside clang
914 if (cxxConstructorDecl
->isDependentContext())
917 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
918 if (insideMoveOrCopyOrCloneDeclParent
&& cxxConstructorDecl
->getParent() == insideMoveOrCopyOrCloneDeclParent
)
921 for(auto it
= cxxConstructorDecl
->init_begin(); it
!= cxxConstructorDecl
->init_end(); ++it
)
923 const CXXCtorInitializer
* init
= *it
;
924 const FieldDecl
* fieldDecl
= init
->getMember();
925 if (fieldDecl
&& init
->getInit() && !isSomeKindOfZero(init
->getInit()))
927 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
928 writeToSet
.insert(fieldInfo
);
934 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
935 // have to do it here.
936 bool UnusedFields::VisitInitListExpr( const InitListExpr
* initListExpr
)
938 if (ignoreLocation( initListExpr
))
941 QualType varType
= initListExpr
->getType().getDesugaredType(compiler
.getASTContext());
942 auto recordType
= varType
->getAs
<RecordType
>();
946 auto recordDecl
= recordType
->getDecl();
947 for (auto it
= recordDecl
->field_begin(); it
!= recordDecl
->field_end(); ++it
)
949 MyFieldInfo fieldInfo
= niceName(*it
);
950 writeToSet
.insert(fieldInfo
);
956 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
958 const Decl
* decl
= declRefExpr
->getDecl();
959 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
963 fieldDecl
= fieldDecl
->getCanonicalDecl();
964 if (ignoreLocation(fieldDecl
)) {
967 // ignore stuff that forms part of the stable URE interface
968 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
971 checkTouchedFromOutside(fieldDecl
, declRefExpr
);
975 void UnusedFields::checkTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
) {
976 const FunctionDecl
* memberExprParentFunction
= getParentFunctionDecl(memberExpr
);
977 const CXXMethodDecl
* methodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberExprParentFunction
);
979 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
981 // it's touched from somewhere outside a class
983 touchedFromOutsideSet
.insert(fieldInfo
);
987 auto constructorDecl
= dyn_cast
<CXXConstructorDecl
>(methodDecl
);
988 if (methodDecl
->isCopyAssignmentOperator() || methodDecl
->isMoveAssignmentOperator()) {
989 // ignore move/copy operator, it's self->self
990 } else if (constructorDecl
&& (constructorDecl
->isCopyConstructor() || constructorDecl
->isMoveConstructor())) {
991 // ignore move/copy constructor, it's self->self
993 if (memberExprParentFunction
->getParent() == fieldDecl
->getParent()) {
994 touchedFromInsideSet
.insert(fieldInfo
);
995 if (!constructorDecl
)
996 touchedFromOutsideConstructorSet
.insert(fieldInfo
);
998 touchedFromOutsideSet
.insert(fieldInfo
);
1003 llvm::Optional
<CalleeWrapper
> UnusedFields::getCallee(CallExpr
const * callExpr
)
1005 FunctionDecl
const * functionDecl
= callExpr
->getDirectCallee();
1007 return CalleeWrapper(functionDecl
);
1009 // Extract the functionprototype from a type
1010 clang::Type
const * calleeType
= callExpr
->getCallee()->getType().getTypePtr();
1011 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>()) {
1012 if (auto prototype
= pointerType
->getPointeeType()->getUnqualifiedDesugaredType()->getAs
<FunctionProtoType
>()) {
1013 return CalleeWrapper(prototype
);
1017 return llvm::Optional
<CalleeWrapper
>();
1020 loplugin::Plugin::Registration
< UnusedFields
> X("unusedfields", false);
1026 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */