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 loplugin::FilteringPlugin
<UnusedFields
>
141 explicit UnusedFields(loplugin::InstantiationData
const & data
):
142 FilteringPlugin(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 checkIfReadFrom(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
163 void checkIfWrittenTo(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
);
164 bool isSomeKindOfZero(const Expr
* arg
);
165 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
);
166 bool IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
167 CalleeWrapper calleeFunctionDecl
);
168 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const *);
170 RecordDecl
* insideMoveOrCopyOrCloneDeclParent
= nullptr;
171 RecordDecl
* insideStreamOutputOperator
= nullptr;
172 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
173 // we store the parent function on the way down the AST.
174 FunctionDecl
* insideFunctionDecl
= nullptr;
175 std::vector
<FieldDecl
const *> insideConditionalCheckOfMemberSet
;
178 void UnusedFields::run()
180 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
182 if (!isUnitTestMode())
184 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
185 // writing to the same logfile
187 for (const MyFieldInfo
& s
: touchedFromInsideSet
)
188 output
+= "inside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
189 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
190 output
+= "outside:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
191 for (const MyFieldInfo
& s
: touchedFromOutsideConstructorSet
)
192 output
+= "outside-constructor:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
193 for (const MyFieldInfo
& s
: readFromSet
)
194 output
+= "read:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
195 for (const MyFieldInfo
& s
: writeToSet
)
196 output
+= "write:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\n";
197 for (const MyFieldInfo
& s
: definitionSet
)
198 output
+= "definition:\t" + s
.access
+ "\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.fieldType
+ "\t" + s
.sourceLocation
+ "\n";
199 std::ofstream myfile
;
200 myfile
.open( WORKDIR
"/loplugin.unusedfields.log", std::ios::app
| std::ios::out
);
206 for (const MyFieldInfo
& s
: readFromSet
)
208 DiagnosticsEngine::Warning
,
210 compat::getBeginLoc(s
.parentRecord
))
212 for (const MyFieldInfo
& s
: writeToSet
)
214 DiagnosticsEngine::Warning
,
216 compat::getBeginLoc(s
.parentRecord
))
222 MyFieldInfo
UnusedFields::niceName(const FieldDecl
* fieldDecl
)
226 const RecordDecl
* recordDecl
= fieldDecl
->getParent();
228 if (const CXXRecordDecl
* cxxRecordDecl
= dyn_cast
<CXXRecordDecl
>(recordDecl
))
230 if (cxxRecordDecl
->getTemplateInstantiationPattern())
231 cxxRecordDecl
= cxxRecordDecl
->getTemplateInstantiationPattern();
232 aInfo
.parentRecord
= cxxRecordDecl
;
233 aInfo
.parentClass
= cxxRecordDecl
->getQualifiedNameAsString();
237 aInfo
.parentRecord
= recordDecl
;
238 aInfo
.parentClass
= recordDecl
->getQualifiedNameAsString();
241 aInfo
.fieldName
= fieldDecl
->getNameAsString();
242 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
243 size_t idx
= aInfo
.fieldName
.find(SRCDIR
);
244 if (idx
!= std::string::npos
) {
245 aInfo
.fieldName
= aInfo
.fieldName
.replace(idx
, strlen(SRCDIR
), "");
247 aInfo
.fieldType
= fieldDecl
->getType().getAsString();
249 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( fieldDecl
->getLocation() );
250 StringRef name
= compiler
.getSourceManager().getFilename(expansionLoc
);
251 aInfo
.sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
252 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
254 switch (fieldDecl
->getAccess())
256 case AS_public
: aInfo
.access
= "public"; break;
257 case AS_private
: aInfo
.access
= "private"; break;
258 case AS_protected
: aInfo
.access
= "protected"; break;
259 default: aInfo
.access
= "unknown"; break;
265 bool UnusedFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
267 fieldDecl
= fieldDecl
->getCanonicalDecl();
268 if (ignoreLocation( fieldDecl
)) {
271 // ignore stuff that forms part of the stable URE interface
272 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
276 if (fieldDecl
->getInClassInitializer() && !isSomeKindOfZero(fieldDecl
->getInClassInitializer())) {
277 writeToSet
.insert(niceName(fieldDecl
));
280 definitionSet
.insert(niceName(fieldDecl
));
285 Does the expression being used to initialise a field value evaluate to
286 the same as a default value?
288 bool UnusedFields::isSomeKindOfZero(const Expr
* arg
)
291 arg
= arg
->IgnoreParenCasts();
292 if (isa
<CXXDefaultArgExpr
>(arg
)) {
293 arg
= dyn_cast
<CXXDefaultArgExpr
>(arg
)->getExpr();
295 arg
= arg
->IgnoreParenCasts();
296 // ignore this, it seems to trigger an infinite recursion
297 if (isa
<UnaryExprOrTypeTraitExpr
>(arg
)) {
300 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(arg
)) {
301 return cxxConstructExpr
->getConstructor()->isDefaultConstructor();
304 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
308 if (isa
<CXXNullPtrLiteralExpr
>(arg
)) {
311 if (isa
<MaterializeTemporaryExpr
>(arg
))
313 const CXXBindTemporaryExpr
* strippedArg
= dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
316 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
317 if (temp
->getNumArgs() == 0)
319 if (loplugin::TypeCheck(temp
->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
322 if (loplugin::TypeCheck(temp
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
330 // Get the expression contents.
331 // This helps us find params which are always initialised with something like "OUString()".
332 SourceManager
& SM
= compiler
.getSourceManager();
333 SourceLocation startLoc
= compat::getBeginLoc(arg
);
334 SourceLocation endLoc
= compat::getEndLoc(arg
);
335 const char *p1
= SM
.getCharacterData( startLoc
);
336 const char *p2
= SM
.getCharacterData( endLoc
);
337 if (!p1
|| !p2
|| (p2
- p1
) < 0 || (p2
- p1
) > 40) {
340 unsigned n
= Lexer::MeasureTokenLength( endLoc
, SM
, compiler
.getLangOpts());
341 std::string
s( p1
, p2
- p1
+ n
);
342 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
343 std::replace( s
.begin(), s
.end(), '\r', ' ');
344 std::replace( s
.begin(), s
.end(), '\n', ' ');
345 std::replace( s
.begin(), s
.end(), '\t', ' ');
347 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
348 if (s
== "OUString()")
350 else if (s
== "OString()")
352 else if (s
== "aEmptyOUStr") //sw
354 else if (s
== "EMPTY_OUSTRING")//sc
356 else if (s
== "GetEmptyOUString()") //sc
361 static char easytolower(char in
)
363 if (in
<='Z' && in
>='A')
368 bool startswith(const std::string
& rStr
, const char* pSubStr
)
370 return rStr
.compare(0, strlen(pSubStr
), pSubStr
) == 0;
373 bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
375 auto copy
= insideMoveOrCopyOrCloneDeclParent
;
376 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
378 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
379 insideMoveOrCopyOrCloneDeclParent
= cxxConstructorDecl
->getParent();
381 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
382 insideMoveOrCopyOrCloneDeclParent
= copy
;
386 bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
388 auto copy1
= insideMoveOrCopyOrCloneDeclParent
;
389 auto copy2
= insideFunctionDecl
;
390 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
392 if (cxxMethodDecl
->isCopyAssignmentOperator()
393 || cxxMethodDecl
->isMoveAssignmentOperator()
394 || (cxxMethodDecl
->getIdentifier() && (cxxMethodDecl
->getName().startswith("Clone") || cxxMethodDecl
->getName().startswith("clone"))))
395 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
396 // these are similar in that they tend to simply enumerate all the fields of an object without putting
397 // them to some useful purpose
398 auto op
= cxxMethodDecl
->getOverloadedOperator();
399 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
400 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
402 insideFunctionDecl
= cxxMethodDecl
;
403 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
404 insideMoveOrCopyOrCloneDeclParent
= copy1
;
405 insideFunctionDecl
= copy2
;
409 bool UnusedFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
411 auto copy1
= insideStreamOutputOperator
;
412 auto copy2
= insideFunctionDecl
;
413 auto copy3
= insideMoveOrCopyOrCloneDeclParent
;
414 if (functionDecl
->getLocation().isValid() && !ignoreLocation(functionDecl
) && functionDecl
->isThisDeclarationADefinition())
416 auto op
= functionDecl
->getOverloadedOperator();
417 if (op
== OO_LessLess
418 && functionDecl
->getNumParams() == 2)
420 QualType qt
= functionDecl
->getParamDecl(1)->getType();
421 insideStreamOutputOperator
= qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
423 // these are similar in that they tend to simply enumerate all the fields of an object without putting
424 // them to some useful purpose
425 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
427 QualType qt
= functionDecl
->getParamDecl(1)->getType();
428 insideMoveOrCopyOrCloneDeclParent
= qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
431 insideFunctionDecl
= functionDecl
;
432 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
433 insideStreamOutputOperator
= copy1
;
434 insideFunctionDecl
= copy2
;
435 insideMoveOrCopyOrCloneDeclParent
= copy3
;
439 bool UnusedFields::TraverseIfStmt(IfStmt
* ifStmt
)
441 FieldDecl
const * memberFieldDecl
= nullptr;
442 Expr
const * cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
443 if (auto memberExpr
= dyn_cast
<MemberExpr
>(cond
))
445 if ((memberFieldDecl
= dyn_cast
<FieldDecl
>(memberExpr
->getMemberDecl())))
446 insideConditionalCheckOfMemberSet
.push_back(memberFieldDecl
);
448 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
450 insideConditionalCheckOfMemberSet
.pop_back();
454 bool UnusedFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
456 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
457 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
461 fieldDecl
= fieldDecl
->getCanonicalDecl();
462 if (ignoreLocation(fieldDecl
)) {
465 // ignore stuff that forms part of the stable URE interface
466 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
470 checkTouchedFromOutside(fieldDecl
, memberExpr
);
472 checkIfReadFrom(fieldDecl
, memberExpr
);
474 checkIfWrittenTo(fieldDecl
, memberExpr
);
479 void UnusedFields::checkIfReadFrom(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
481 if (insideMoveOrCopyOrCloneDeclParent
|| insideStreamOutputOperator
)
483 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
484 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
485 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
487 // we don't care about reads when the field is being used in an output operator, this is normally
489 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideStreamOutputOperator
))
493 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
494 const Stmt
* child
= memberExpr
;
495 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
496 // walk up the tree until we find something interesting
497 bool bPotentiallyReadFrom
= false;
499 auto walkUp
= [&]() {
501 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
502 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
508 // check if we're inside a CXXCtorInitializer or a VarDecl
509 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
510 if ( parentsRange
.begin() != parentsRange
.end())
512 const Decl
* decl
= parentsRange
.begin()->get
<Decl
>();
513 if (decl
&& (isa
<CXXConstructorDecl
>(decl
) || isa
<VarDecl
>(decl
)))
514 bPotentiallyReadFrom
= true;
516 if (!bPotentiallyReadFrom
)
520 if (isa
<CXXReinterpretCastExpr
>(parent
))
522 // once we see one of these, there is not much useful we can know
523 bPotentiallyReadFrom
= true;
526 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
527 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
531 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
533 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
534 if (memberExpr
->getType()->isArrayType() && op
== UO_Deref
)
536 // ignore, deref'ing an array does not count as a read
538 else if (op
== UO_AddrOf
|| op
== UO_Deref
539 || op
== UO_Plus
|| op
== UO_Minus
540 || op
== UO_Not
|| op
== UO_LNot
)
542 bPotentiallyReadFrom
= true;
545 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
546 ignore them to find interesting fields that only modified, not usefully read:
547 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
548 But we still walk up in case the result of the expression is used in a read sense.
552 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
554 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
557 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
559 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
562 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
564 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
567 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
569 if (arraySubscriptExpr
->getIdx() == child
)
571 bPotentiallyReadFrom
= true;
576 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
578 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
579 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
580 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
581 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
582 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
583 if (binaryOp
->getLHS() == child
&& assignmentOp
)
587 bPotentiallyReadFrom
= true;
591 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
593 auto op
= operatorCallExpr
->getOperator();
594 const bool assignmentOp
= op
== OO_Equal
|| op
== OO_StarEqual
||
595 op
== OO_SlashEqual
|| op
== OO_PercentEqual
||
596 op
== OO_PlusEqual
|| op
== OO_MinusEqual
||
597 op
== OO_LessLessEqual
||
598 op
== OO_AmpEqual
|| op
== OO_CaretEqual
||
600 if (operatorCallExpr
->getArg(0) == child
&& assignmentOp
)
602 else if (op
== OO_GreaterGreaterEqual
&& operatorCallExpr
->getArg(1) == child
)
603 break; // this is a write-only call
606 bPotentiallyReadFrom
= true;
610 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
612 // check for calls to ReadXXX() type methods and the operator>>= methods on Any.
613 auto callee
= getCallee(callExpr
);
616 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
617 // which we could scatter around.
618 std::string name
= callee
->getNameAsString();
619 std::transform(name
.begin(), name
.end(), name
.begin(), easytolower
);
620 if (startswith(name
, "read"))
621 // this is a write-only call
623 else if (startswith(name
, "emplace") || name
== "insert"
624 || name
== "erase" || name
== "remove" || name
== "remove_if" || name
== "sort"
625 || name
== "push_back" || name
== "pop_back"
626 || name
== "push_front" || name
== "pop_front"
627 || name
== "reserve" || name
== "resize"
628 || name
== "clear" || name
== "fill")
629 // write-only modifications to collections
631 else if (name
== "dispose" || name
== "disposeAndClear" || name
== "swap")
632 // we're abusing the write-only analysis here to look for fields which don't have anything useful
633 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
634 // and VclPtr::disposeAndClear
637 bPotentiallyReadFrom
= true;
640 bPotentiallyReadFrom
= true;
643 else if (isa
<ReturnStmt
>(parent
)
644 || isa
<CXXConstructExpr
>(parent
)
645 || isa
<ConditionalOperator
>(parent
)
646 || isa
<SwitchStmt
>(parent
)
647 || isa
<DeclStmt
>(parent
)
648 || isa
<WhileStmt
>(parent
)
649 || isa
<CXXNewExpr
>(parent
)
650 || isa
<ForStmt
>(parent
)
651 || isa
<InitListExpr
>(parent
)
652 || isa
<CXXDependentScopeMemberExpr
>(parent
)
653 || isa
<UnresolvedMemberExpr
>(parent
)
654 || isa
<MaterializeTemporaryExpr
>(parent
))
656 bPotentiallyReadFrom
= true;
659 else if (isa
<CXXDeleteExpr
>(parent
)
660 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
661 || isa
<CXXUnresolvedConstructExpr
>(parent
)
662 || isa
<CompoundStmt
>(parent
)
663 || isa
<LabelStmt
>(parent
)
664 || isa
<CXXForRangeStmt
>(parent
)
665 || isa
<CXXTypeidExpr
>(parent
)
666 || isa
<DefaultStmt
>(parent
))
672 bPotentiallyReadFrom
= true;
681 DiagnosticsEngine::Warning
,
682 "oh dear, what can the matter be?",
683 compat::getBeginLoc(memberExpr
))
684 << memberExpr
->getSourceRange();
686 DiagnosticsEngine::Note
,
688 compat::getBeginLoc(parent
))
689 << parent
->getSourceRange();
694 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
695 if (bPotentiallyReadFrom
)
697 readFromSet
.insert(fieldInfo
);
701 void UnusedFields::checkIfWrittenTo(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
703 if (insideMoveOrCopyOrCloneDeclParent
)
705 RecordDecl
const * cxxRecordDecl1
= fieldDecl
->getParent();
706 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
707 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
713 // if we're inside a block that looks like
716 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
717 if (std::find(insideConditionalCheckOfMemberSet
.begin(), insideConditionalCheckOfMemberSet
.end(), fieldDecl
) != insideConditionalCheckOfMemberSet
.end())
720 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
721 const Stmt
* child
= memberExpr
;
722 const Stmt
* parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
723 // walk up the tree until we find something interesting
724 bool bPotentiallyWrittenTo
= false;
726 auto walkUp
= [&]() {
728 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
729 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
735 // check if we have an expression like
737 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
738 if (parentsRange
.begin() != parentsRange
.end())
740 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
741 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
742 // which is of type 'T&&' and also an l-value-ref ?
743 if (varDecl
&& !varDecl
->isImplicit() && loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
745 bPotentiallyWrittenTo
= true;
750 if (isa
<CXXReinterpretCastExpr
>(parent
))
752 // once we see one of these, there is not much useful we can know
753 bPotentiallyWrittenTo
= true;
756 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
757 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
761 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
763 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
764 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
|| op
== UO_PreDec
)
766 bPotentiallyWrittenTo
= true;
770 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
772 if (arraySubscriptExpr
->getIdx() == child
)
776 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
778 auto callee
= getCallee(operatorCallExpr
);
781 // if calling a non-const operator on the field
782 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
783 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
)
785 if (!calleeMethodDecl
->isConst())
786 bPotentiallyWrittenTo
= checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
788 else if (IsPassedByNonConst(fieldDecl
, child
, operatorCallExpr
, *callee
))
790 bPotentiallyWrittenTo
= true;
794 bPotentiallyWrittenTo
= true; // conservative, could improve
797 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
799 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
800 if (calleeMethodDecl
)
802 // if calling a non-const method on the field
803 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
804 if (tmp
->isBoundMemberFunction(compiler
.getASTContext())) {
805 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
807 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
809 if (!calleeMethodDecl
->isConst())
810 bPotentiallyWrittenTo
= checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
813 else if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
, CalleeWrapper(calleeMethodDecl
)))
814 bPotentiallyWrittenTo
= true;
817 bPotentiallyWrittenTo
= true; // can happen in templates
820 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
822 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
, CalleeWrapper(cxxConstructExpr
)))
823 bPotentiallyWrittenTo
= true;
826 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
828 auto callee
= getCallee(callExpr
);
830 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
831 bPotentiallyWrittenTo
= true;
833 bPotentiallyWrittenTo
= true; // conservative, could improve
836 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
838 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
839 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
840 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
841 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
842 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
;
845 if (binaryOp
->getLHS() == child
)
846 bPotentiallyWrittenTo
= true;
847 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType()).LvalueReference().NonConst())
848 // if the LHS is a non-const reference, we could write to the field later on
849 bPotentiallyWrittenTo
= true;
853 else if (isa
<ReturnStmt
>(parent
))
855 if (insideFunctionDecl
)
857 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
858 if (tc
.LvalueReference().NonConst())
859 bPotentiallyWrittenTo
= true;
863 else if (isa
<ConditionalOperator
>(parent
)
864 || isa
<SwitchStmt
>(parent
)
865 || isa
<DeclStmt
>(parent
)
866 || isa
<WhileStmt
>(parent
)
867 || isa
<CXXNewExpr
>(parent
)
868 || isa
<ForStmt
>(parent
)
869 || isa
<InitListExpr
>(parent
)
870 || isa
<CXXDependentScopeMemberExpr
>(parent
)
871 || isa
<UnresolvedMemberExpr
>(parent
)
872 || isa
<MaterializeTemporaryExpr
>(parent
)
873 || isa
<IfStmt
>(parent
)
874 || isa
<DoStmt
>(parent
)
875 || isa
<CXXDeleteExpr
>(parent
)
876 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
877 || isa
<CXXUnresolvedConstructExpr
>(parent
)
878 || isa
<CompoundStmt
>(parent
)
879 || isa
<LabelStmt
>(parent
)
880 || isa
<CXXForRangeStmt
>(parent
)
881 || isa
<CXXTypeidExpr
>(parent
)
882 || isa
<DefaultStmt
>(parent
))
888 bPotentiallyWrittenTo
= true;
897 DiagnosticsEngine::Warning
,
898 "oh dear, what can the matter be? writtenTo=%0",
899 compat::getBeginLoc(memberExpr
))
900 << bPotentiallyWrittenTo
901 << memberExpr
->getSourceRange();
905 DiagnosticsEngine::Note
,
907 compat::getBeginLoc(parent
))
908 << parent
->getSourceRange();
912 fieldDecl
->getType()->dump();
915 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
916 if (bPotentiallyWrittenTo
)
918 writeToSet
.insert(fieldInfo
);
922 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
923 bool UnusedFields::checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
)
925 auto const tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
926 bool listLike
= false, setLike
= false, mapLike
= false, cssSequence
= false;
927 if (tc
.Class("deque").StdNamespace()
928 || tc
.Class("list").StdNamespace()
929 || tc
.Class("queue").StdNamespace()
930 || tc
.Class("vector").StdNamespace())
934 else if (tc
.Class("set").StdNamespace()
935 || tc
.Class("unordered_set").StdNamespace())
939 else if (tc
.Class("map").StdNamespace()
940 || tc
.Class("unordered_map").StdNamespace())
944 else if (tc
.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
951 if (calleeMethodDecl
->isOverloadedOperator())
953 auto oo
= calleeMethodDecl
->getOverloadedOperator();
956 // This is operator[]. We only care about things that add elements to the collection.
957 // if nothing modifies the size of the collection, then nothing useful
964 auto name
= calleeMethodDecl
->getName();
965 if (listLike
|| setLike
|| mapLike
)
967 if (name
== "reserve" || name
== "shrink_to_fit" || name
== "clear"
968 || name
== "erase" || name
== "pop_back" || name
== "pop_front"
969 || name
== "front" || name
== "back" || name
== "data"
970 || name
== "remove" || name
== "remove_if"
971 || name
== "unique" || name
== "sort"
972 || name
== "begin" || name
== "end"
973 || name
== "rbegin" || name
== "rend"
974 || name
== "at" || name
== "find" || name
== "equal_range"
975 || name
== "lower_bound" || name
== "upper_bound")
980 if (name
== "getArray" || name
== "begin" || name
== "end")
987 bool UnusedFields::IsPassedByNonConst(const FieldDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
988 CalleeWrapper calleeFunctionDecl
)
990 unsigned len
= std::min(callExpr
.getNumArgs(),
991 calleeFunctionDecl
.getNumParams());
992 // if it's an array, passing it by value to a method typically means the
993 // callee takes a pointer and can modify the array
994 if (fieldDecl
->getType()->isConstantArrayType())
996 for (unsigned i
= 0; i
< len
; ++i
)
997 if (callExpr
.getArg(i
) == child
)
998 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
1003 for (unsigned i
= 0; i
< len
; ++i
)
1004 if (callExpr
.getArg(i
) == child
)
1005 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).LvalueReference().NonConst())
1011 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
1012 // have to do it here
1013 bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl
* cxxConstructorDecl
)
1015 if (ignoreLocation( cxxConstructorDecl
)) {
1018 // ignore stuff that forms part of the stable URE interface
1019 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(cxxConstructorDecl
->getLocation()))) {
1023 // templates make EvaluateAsInt crash inside clang
1024 if (cxxConstructorDecl
->isDependentContext())
1027 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
1028 if (insideMoveOrCopyOrCloneDeclParent
&& cxxConstructorDecl
->getParent() == insideMoveOrCopyOrCloneDeclParent
)
1031 for(auto it
= cxxConstructorDecl
->init_begin(); it
!= cxxConstructorDecl
->init_end(); ++it
)
1033 const CXXCtorInitializer
* init
= *it
;
1034 const FieldDecl
* fieldDecl
= init
->getMember();
1035 if (fieldDecl
&& init
->getInit() && !isSomeKindOfZero(init
->getInit()))
1037 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
1038 writeToSet
.insert(fieldInfo
);
1044 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
1045 // have to do it here.
1046 bool UnusedFields::VisitInitListExpr( const InitListExpr
* initListExpr
)
1048 if (ignoreLocation( initListExpr
))
1051 QualType varType
= initListExpr
->getType().getDesugaredType(compiler
.getASTContext());
1052 auto recordType
= varType
->getAs
<RecordType
>();
1056 auto recordDecl
= recordType
->getDecl();
1057 for (auto it
= recordDecl
->field_begin(); it
!= recordDecl
->field_end(); ++it
)
1059 MyFieldInfo fieldInfo
= niceName(*it
);
1060 writeToSet
.insert(fieldInfo
);
1066 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
1068 const Decl
* decl
= declRefExpr
->getDecl();
1069 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
1073 fieldDecl
= fieldDecl
->getCanonicalDecl();
1074 if (ignoreLocation(fieldDecl
)) {
1077 // ignore stuff that forms part of the stable URE interface
1078 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
1081 checkTouchedFromOutside(fieldDecl
, declRefExpr
);
1085 void UnusedFields::checkTouchedFromOutside(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
) {
1086 const FunctionDecl
* memberExprParentFunction
= getParentFunctionDecl(memberExpr
);
1087 const CXXMethodDecl
* methodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberExprParentFunction
);
1089 MyFieldInfo fieldInfo
= niceName(fieldDecl
);
1091 // it's touched from somewhere outside a class
1093 touchedFromOutsideSet
.insert(fieldInfo
);
1097 auto constructorDecl
= dyn_cast
<CXXConstructorDecl
>(methodDecl
);
1098 if (methodDecl
->isCopyAssignmentOperator() || methodDecl
->isMoveAssignmentOperator()) {
1099 // ignore move/copy operator, it's self->self
1100 } else if (constructorDecl
&& (constructorDecl
->isCopyConstructor() || constructorDecl
->isMoveConstructor())) {
1101 // ignore move/copy constructor, it's self->self
1103 if (memberExprParentFunction
->getParent() == fieldDecl
->getParent()) {
1104 touchedFromInsideSet
.insert(fieldInfo
);
1105 if (!constructorDecl
)
1106 touchedFromOutsideConstructorSet
.insert(fieldInfo
);
1108 touchedFromOutsideSet
.insert(fieldInfo
);
1113 llvm::Optional
<CalleeWrapper
> UnusedFields::getCallee(CallExpr
const * callExpr
)
1115 FunctionDecl
const * functionDecl
= callExpr
->getDirectCallee();
1117 return CalleeWrapper(functionDecl
);
1119 // Extract the functionprototype from a type
1120 clang::Type
const * calleeType
= callExpr
->getCallee()->getType().getTypePtr();
1121 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>()) {
1122 if (auto prototype
= pointerType
->getPointeeType()->getUnqualifiedDesugaredType()->getAs
<FunctionProtoType
>()) {
1123 return CalleeWrapper(prototype
);
1127 return llvm::Optional
<CalleeWrapper
>();
1130 loplugin::Plugin::Registration
< UnusedFields
> X("unusedfields", false);
1136 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */