bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / unusedfields.cxx
bloba0796fe9554714977ef120c3bbbfead4525a818c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <unordered_set>
17 #include <vector>
18 #include <algorithm>
19 #include <sys/file.h>
20 #include <unistd.h>
21 #include "plugin.hxx"
22 #include "compat.hxx"
23 #include "check.hxx"
25 /**
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:
36 $ make check
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedfields' check
38 $ ./compilerplugins/clang/unusedfields.py
40 and then
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
45 to get it to work :-)
49 namespace {
51 struct MyFieldInfo
53 const RecordDecl* parentRecord;
54 std::string parentClass;
55 std::string fieldName;
56 std::string fieldType;
57 std::string sourceLocation;
58 std::string access;
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;
75 /**
76 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
78 class CallerWrapper
80 const CallExpr * m_callExpr;
81 const CXXConstructExpr * m_cxxConstructExpr;
82 public:
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); }
90 class CalleeWrapper
92 const FunctionDecl * m_calleeFunctionDecl = nullptr;
93 const CXXConstructorDecl * m_cxxConstructorDecl = nullptr;
94 const FunctionProtoType * m_functionPrototype = nullptr;
95 public:
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
107 return 0;
108 else
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();
117 else
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();
126 else
127 return "";
129 CXXMethodDecl const * getAsCXXMethodDecl() const
131 if (m_calleeFunctionDecl)
132 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
133 return nullptr;
137 class UnusedFields:
138 public loplugin::FilteringPlugin<UnusedFields>
140 public:
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* );
159 private:
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
186 std::string output;
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);
201 myfile << output;
202 myfile.close();
204 else
206 for (const MyFieldInfo & s : readFromSet)
207 report(
208 DiagnosticsEngine::Warning,
209 "read %0",
210 compat::getBeginLoc(s.parentRecord))
211 << s.fieldName;
212 for (const MyFieldInfo & s : writeToSet)
213 report(
214 DiagnosticsEngine::Warning,
215 "write %0",
216 compat::getBeginLoc(s.parentRecord))
217 << s.fieldName;
222 MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
224 MyFieldInfo aInfo;
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();
235 else
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;
262 return aInfo;
265 bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
267 fieldDecl = fieldDecl->getCanonicalDecl();
268 if (ignoreLocation( fieldDecl )) {
269 return true;
271 // ignore stuff that forms part of the stable URE interface
272 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
273 return true;
276 if (fieldDecl->getInClassInitializer() && !isSomeKindOfZero(fieldDecl->getInClassInitializer())) {
277 writeToSet.insert(niceName(fieldDecl));
280 definitionSet.insert(niceName(fieldDecl));
281 return true;
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)
290 assert(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)) {
298 return false;
300 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg)) {
301 return cxxConstructExpr->getConstructor()->isDefaultConstructor();
303 APSInt x1;
304 if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
306 return x1 == 0;
308 if (isa<CXXNullPtrLiteralExpr>(arg)) {
309 return true;
311 if (isa<MaterializeTemporaryExpr>(arg))
313 const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
314 if (strippedArg)
316 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
317 if (temp->getNumArgs() == 0)
319 if (loplugin::TypeCheck(temp->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
320 return true;
322 if (loplugin::TypeCheck(temp->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
323 return true;
325 return false;
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) {
338 return false;
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()")
349 return true;
350 else if (s == "OString()")
351 return true;
352 else if (s == "aEmptyOUStr") //sw
353 return true;
354 else if (s == "EMPTY_OUSTRING")//sc
355 return true;
356 else if (s == "GetEmptyOUString()") //sc
357 return true;
358 return false;
361 static char easytolower(char in)
363 if (in<='Z' && in>='A')
364 return in-('Z'-'z');
365 return in;
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;
383 return ret;
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;
406 return ret;
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;
436 return ret;
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);
449 if (memberFieldDecl)
450 insideConditionalCheckOfMemberSet.pop_back();
451 return ret;
454 bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
456 const ValueDecl* decl = memberExpr->getMemberDecl();
457 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
458 if (!fieldDecl) {
459 return true;
461 fieldDecl = fieldDecl->getCanonicalDecl();
462 if (ignoreLocation(fieldDecl)) {
463 return true;
465 // ignore stuff that forms part of the stable URE interface
466 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
467 return true;
470 checkTouchedFromOutside(fieldDecl, memberExpr);
472 checkIfReadFrom(fieldDecl, memberExpr);
474 checkIfWrittenTo(fieldDecl, memberExpr);
476 return true;
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))
486 return;
487 // we don't care about reads when the field is being used in an output operator, this is normally
488 // debug stuff
489 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator))
490 return;
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;
498 bool bDump = false;
499 auto walkUp = [&]() {
500 child = parent;
501 auto parentsRange = compiler.getASTContext().getParents(*parent);
502 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
506 if (!parent)
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)
517 return;
518 break;
520 if (isa<CXXReinterpretCastExpr>(parent))
522 // once we see one of these, there is not much useful we can know
523 bPotentiallyReadFrom = true;
524 break;
526 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
527 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
529 walkUp();
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;
543 break;
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.
550 walkUp();
552 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
554 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
555 break;
557 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
559 bPotentiallyReadFrom = ifStmt->getCond() == child;
560 break;
562 else if (auto doStmt = dyn_cast<DoStmt>(parent))
564 bPotentiallyReadFrom = doStmt->getCond() == child;
565 break;
567 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
569 if (arraySubscriptExpr->getIdx() == child)
571 bPotentiallyReadFrom = true;
572 break;
574 walkUp();
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)
584 break;
585 else
587 bPotentiallyReadFrom = true;
588 break;
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 ||
599 op == OO_PipeEqual;
600 if (operatorCallExpr->getArg(0) == child && assignmentOp)
601 break;
602 else if (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child)
603 break; // this is a write-only call
604 else
606 bPotentiallyReadFrom = true;
607 break;
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);
614 if (callee)
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
636 else
637 bPotentiallyReadFrom = true;
639 else
640 bPotentiallyReadFrom = true;
641 break;
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;
657 break;
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))
668 break;
670 else
672 bPotentiallyReadFrom = true;
673 bDump = true;
674 break;
676 } while (true);
678 if (bDump)
680 report(
681 DiagnosticsEngine::Warning,
682 "oh dear, what can the matter be?",
683 compat::getBeginLoc(memberExpr))
684 << memberExpr->getSourceRange();
685 report(
686 DiagnosticsEngine::Note,
687 "parent over here",
688 compat::getBeginLoc(parent))
689 << parent->getSourceRange();
690 parent->dump();
691 memberExpr->dump();
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))
709 return;
713 // if we're inside a block that looks like
714 // if (fieldDecl)
715 // ....
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())
718 return;
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;
725 bool bDump = false;
726 auto walkUp = [&]() {
727 child = parent;
728 auto parentsRange = compiler.getASTContext().getParents(*parent);
729 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
733 if (!parent)
735 // check if we have an expression like
736 // int& r = m_field;
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;
748 break;
750 if (isa<CXXReinterpretCastExpr>(parent))
752 // once we see one of these, there is not much useful we can know
753 bPotentiallyWrittenTo = true;
754 break;
756 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
757 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
759 walkUp();
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;
768 break;
770 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
772 if (arraySubscriptExpr->getIdx() == child)
773 break;
774 walkUp();
776 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
778 auto callee = getCallee(operatorCallExpr);
779 if (callee)
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;
793 else
794 bPotentiallyWrittenTo = true; // conservative, could improve
795 break;
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);
811 break;
813 else if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr, CalleeWrapper(calleeMethodDecl)))
814 bPotentiallyWrittenTo = true;
816 else
817 bPotentiallyWrittenTo = true; // can happen in templates
818 break;
820 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
822 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr, CalleeWrapper(cxxConstructExpr)))
823 bPotentiallyWrittenTo = true;
824 break;
826 else if (auto callExpr = dyn_cast<CallExpr>(parent))
828 auto callee = getCallee(callExpr);
829 if (callee) {
830 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
831 bPotentiallyWrittenTo = true;
832 } else
833 bPotentiallyWrittenTo = true; // conservative, could improve
834 break;
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;
843 if (assignmentOp)
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;
851 break;
853 else if (isa<ReturnStmt>(parent))
855 if (insideFunctionDecl)
857 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
858 if (tc.LvalueReference().NonConst())
859 bPotentiallyWrittenTo = true;
861 break;
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))
884 break;
886 else
888 bPotentiallyWrittenTo = true;
889 bDump = true;
890 break;
892 } while (true);
894 if (bDump)
896 report(
897 DiagnosticsEngine::Warning,
898 "oh dear, what can the matter be? writtenTo=%0",
899 compat::getBeginLoc(memberExpr))
900 << bPotentiallyWrittenTo
901 << memberExpr->getSourceRange();
902 if (parent)
904 report(
905 DiagnosticsEngine::Note,
906 "parent over here",
907 compat::getBeginLoc(parent))
908 << parent->getSourceRange();
909 parent->dump();
911 memberExpr->dump();
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())
932 listLike = true;
934 else if (tc.Class("set").StdNamespace()
935 || tc.Class("unordered_set").StdNamespace())
937 setLike = true;
939 else if (tc.Class("map").StdNamespace()
940 || tc.Class("unordered_map").StdNamespace())
942 mapLike = true;
944 else if (tc.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
946 cssSequence = true;
948 else
949 return true;
951 if (calleeMethodDecl->isOverloadedOperator())
953 auto oo = calleeMethodDecl->getOverloadedOperator();
954 if (oo == OO_Equal)
955 return true;
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
958 // is stored in it.
959 if (listLike)
960 return false;
961 return true;
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")
976 return false;
978 if (cssSequence)
980 if (name == "getArray" || name == "begin" || name == "end")
981 return false;
984 return true;
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())
999 return true;
1001 else
1003 for (unsigned i = 0; i < len; ++i)
1004 if (callExpr.getArg(i) == child)
1005 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).LvalueReference().NonConst())
1006 return true;
1008 return false;
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 )) {
1016 return true;
1018 // ignore stuff that forms part of the stable URE interface
1019 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation()))) {
1020 return true;
1023 // templates make EvaluateAsInt crash inside clang
1024 if (cxxConstructorDecl->isDependentContext())
1025 return true;
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)
1029 return true;
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);
1041 return true;
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 ))
1049 return true;
1051 QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext());
1052 auto recordType = varType->getAs<RecordType>();
1053 if (!recordType)
1054 return true;
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);
1063 return true;
1066 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
1068 const Decl* decl = declRefExpr->getDecl();
1069 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
1070 if (!fieldDecl) {
1071 return true;
1073 fieldDecl = fieldDecl->getCanonicalDecl();
1074 if (ignoreLocation(fieldDecl)) {
1075 return true;
1077 // ignore stuff that forms part of the stable URE interface
1078 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
1079 return true;
1081 checkTouchedFromOutside(fieldDecl, declRefExpr);
1082 return true;
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
1092 if (!methodDecl) {
1093 touchedFromOutsideSet.insert(fieldInfo);
1094 return;
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
1102 } else {
1103 if (memberExprParentFunction->getParent() == fieldDecl->getParent()) {
1104 touchedFromInsideSet.insert(fieldInfo);
1105 if (!constructorDecl)
1106 touchedFromOutsideConstructorSet.insert(fieldInfo);
1107 } else {
1108 touchedFromOutsideSet.insert(fieldInfo);
1113 llvm::Optional<CalleeWrapper> UnusedFields::getCallee(CallExpr const * callExpr)
1115 FunctionDecl const * functionDecl = callExpr->getDirectCallee();
1116 if (functionDecl)
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);
1134 #endif
1136 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */