Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / unusedfields.cxx
blob159d8544b635e525f2c6581ba10f78eb94b12642
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 RecursiveASTVisitor<UnusedFields>, public loplugin::Plugin
140 public:
141 explicit UnusedFields(loplugin::InstantiationData const & data):
142 Plugin(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 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
185 std::string output;
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);
200 myfile << output;
201 myfile.close();
203 else
205 for (const MyFieldInfo & s : readFromSet)
206 report(
207 DiagnosticsEngine::Warning,
208 "read %0",
209 s.parentRecord->getLocStart())
210 << s.fieldName;
211 for (const MyFieldInfo & s : writeToSet)
212 report(
213 DiagnosticsEngine::Warning,
214 "write %0",
215 s.parentRecord->getLocStart())
216 << s.fieldName;
221 MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
223 MyFieldInfo aInfo;
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();
234 else
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;
261 return aInfo;
264 bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
266 fieldDecl = fieldDecl->getCanonicalDecl();
267 if (ignoreLocation( fieldDecl )) {
268 return true;
270 // ignore stuff that forms part of the stable URE interface
271 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
272 return true;
275 if (fieldDecl->getInClassInitializer() && !isSomeKindOfZero(fieldDecl->getInClassInitializer())) {
276 writeToSet.insert(niceName(fieldDecl));
279 definitionSet.insert(niceName(fieldDecl));
280 return true;
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)
289 assert(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)) {
297 return false;
299 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg)) {
300 return cxxConstructExpr->getConstructor()->isDefaultConstructor();
302 APSInt x1;
303 if (arg->EvaluateAsInt(x1, compiler.getASTContext()))
305 return x1 == 0;
307 if (isa<CXXNullPtrLiteralExpr>(arg)) {
308 return true;
310 if (isa<MaterializeTemporaryExpr>(arg))
312 const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
313 if (strippedArg)
315 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
316 if (temp->getNumArgs() == 0)
318 if (loplugin::TypeCheck(temp->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
319 return true;
321 if (loplugin::TypeCheck(temp->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
322 return true;
324 return false;
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) {
337 return false;
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()")
348 return true;
349 else if (s == "OString()")
350 return true;
351 else if (s == "aEmptyOUStr") //sw
352 return true;
353 else if (s == "EMPTY_OUSTRING")//sc
354 return true;
355 else if (s == "GetEmptyOUString()") //sc
356 return true;
357 return false;
360 static char easytolower(char in)
362 if (in<='Z' && in>='A')
363 return in-('Z'-'z');
364 return in;
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;
382 return ret;
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;
400 return ret;
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;
420 return ret;
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);
433 if (memberFieldDecl)
434 insideConditionalCheckOfMemberSet.pop_back();
435 return ret;
438 bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
440 const ValueDecl* decl = memberExpr->getMemberDecl();
441 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
442 if (!fieldDecl) {
443 return true;
445 fieldDecl = fieldDecl->getCanonicalDecl();
446 if (ignoreLocation(fieldDecl)) {
447 return true;
449 // ignore stuff that forms part of the stable URE interface
450 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
451 return true;
454 checkTouchedFromOutside(fieldDecl, memberExpr);
456 checkWriteOnly(fieldDecl, memberExpr);
458 checkReadOnly(fieldDecl, memberExpr);
460 return true;
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))
470 return;
471 // we don't care about reads when the field is being used in an output operator, this is normally
472 // debug stuff
473 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator))
474 return;
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;
482 bool bDump = false;
483 auto walkupUp = [&]() {
484 child = parent;
485 auto parentsRange = compiler.getASTContext().getParents(*parent);
486 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
490 if (!parent)
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)
501 return;
502 break;
504 if (isa<CXXReinterpretCastExpr>(parent))
506 // once we see one of these, there is not much useful we can know
507 bPotentiallyReadFrom = true;
508 break;
510 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
511 #if CLANG_VERSION >= 40000
512 || isa<ArrayInitLoopExpr>(parent)
513 #endif
514 || isa<ExprWithCleanups>(parent))
516 walkupUp();
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;
532 break;
534 walkupUp();
536 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
538 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
539 break;
541 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
543 bPotentiallyReadFrom = ifStmt->getCond() == child;
544 break;
546 else if (auto doStmt = dyn_cast<DoStmt>(parent))
548 bPotentiallyReadFrom = doStmt->getCond() == child;
549 break;
551 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
553 if (arraySubscriptExpr->getIdx() == child)
555 bPotentiallyReadFrom = true;
556 break;
558 walkupUp();
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);
564 if (callee)
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
581 else
582 bPotentiallyReadFrom = true;
584 else
585 bPotentiallyReadFrom = true;
586 break;
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;
599 break;
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;
615 break;
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))
626 break;
628 else
630 bPotentiallyReadFrom = true;
631 bDump = true;
632 break;
634 } while (true);
636 if (bDump)
638 report(
639 DiagnosticsEngine::Warning,
640 "oh dear, what can the matter be?",
641 memberExpr->getLocStart())
642 << memberExpr->getSourceRange();
643 report(
644 DiagnosticsEngine::Note,
645 "parent over here",
646 parent->getLocStart())
647 << parent->getSourceRange();
648 parent->dump();
649 memberExpr->dump();
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))
664 return;
667 // if we're inside a block that looks like
668 // if (fieldDecl)
669 // ....
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())
672 return;
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;
679 bool bDump = false;
680 auto walkupUp = [&]() {
681 child = parent;
682 auto parentsRange = compiler.getASTContext().getParents(*parent);
683 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
687 if (!parent)
689 // check if we have an expression like
690 // int& r = m_field;
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;
702 break;
704 if (isa<CXXReinterpretCastExpr>(parent))
706 // once we see one of these, there is not much useful we can know
707 bPotentiallyWrittenTo = true;
708 break;
710 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
711 #if CLANG_VERSION >= 40000
712 || isa<ArrayInitLoopExpr>(parent)
713 #endif
714 || isa<ExprWithCleanups>(parent))
716 walkupUp();
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;
725 break;
727 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
729 if (arraySubscriptExpr->getIdx() == child)
730 break;
731 walkupUp();
733 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
735 auto callee = getCallee(operatorCallExpr);
736 if (callee)
738 // if calling a non-const operator on the field
739 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
740 if (calleeMethodDecl
741 && operatorCallExpr->getArg(0) == child && !calleeMethodDecl->isConst())
743 bPotentiallyWrittenTo = true;
745 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
747 bPotentiallyWrittenTo = true;
750 else
751 bPotentiallyWrittenTo = true; // conservative, could improve
752 break;
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;
768 break;
770 if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr, CalleeWrapper(calleeMethodDecl)))
771 bPotentiallyWrittenTo = true;
773 else
774 bPotentiallyWrittenTo = true; // can happen in templates
775 break;
777 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
779 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr, CalleeWrapper(cxxConstructExpr)))
780 bPotentiallyWrittenTo = true;
781 break;
783 else if (auto callExpr = dyn_cast<CallExpr>(parent))
785 auto callee = getCallee(callExpr);
786 if (callee) {
787 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
788 bPotentiallyWrittenTo = true;
789 } else
790 bPotentiallyWrittenTo = true; // conservative, could improve
791 break;
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;
800 if (assignmentOp)
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;
808 break;
810 else if (isa<ReturnStmt>(parent))
812 if (insideFunctionDecl)
814 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
815 if (tc.LvalueReference().NonConst())
816 bPotentiallyWrittenTo = true;
818 break;
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))
841 break;
843 else
845 bPotentiallyWrittenTo = true;
846 bDump = true;
847 break;
849 } while (true);
851 if (bDump)
853 report(
854 DiagnosticsEngine::Warning,
855 "oh dear, what can the matter be? writtenTo=%0",
856 memberExpr->getLocStart())
857 << bPotentiallyWrittenTo
858 << memberExpr->getSourceRange();
859 if (parent)
861 report(
862 DiagnosticsEngine::Note,
863 "parent over here",
864 parent->getLocStart())
865 << parent->getSourceRange();
866 parent->dump();
868 memberExpr->dump();
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())
889 return true;
891 else
893 for (unsigned i = 0; i < len; ++i)
894 if (callExpr.getArg(i) == child)
895 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).LvalueReference().NonConst())
896 return true;
898 return false;
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 )) {
906 return true;
908 // ignore stuff that forms part of the stable URE interface
909 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation()))) {
910 return true;
913 // templates make EvaluateAsInt crash inside clang
914 if (cxxConstructorDecl->isDependentContext())
915 return true;
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)
919 return true;
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);
931 return true;
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 ))
939 return true;
941 QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext());
942 auto recordType = varType->getAs<RecordType>();
943 if (!recordType)
944 return true;
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);
953 return true;
956 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
958 const Decl* decl = declRefExpr->getDecl();
959 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
960 if (!fieldDecl) {
961 return true;
963 fieldDecl = fieldDecl->getCanonicalDecl();
964 if (ignoreLocation(fieldDecl)) {
965 return true;
967 // ignore stuff that forms part of the stable URE interface
968 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
969 return true;
971 checkTouchedFromOutside(fieldDecl, declRefExpr);
972 return true;
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
982 if (!methodDecl) {
983 touchedFromOutsideSet.insert(fieldInfo);
984 return;
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
992 } else {
993 if (memberExprParentFunction->getParent() == fieldDecl->getParent()) {
994 touchedFromInsideSet.insert(fieldInfo);
995 if (!constructorDecl)
996 touchedFromOutsideConstructorSet.insert(fieldInfo);
997 } else {
998 touchedFromOutsideSet.insert(fieldInfo);
1003 llvm::Optional<CalleeWrapper> UnusedFields::getCallee(CallExpr const * callExpr)
1005 FunctionDecl const * functionDecl = callExpr->getDirectCallee();
1006 if (functionDecl)
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);
1024 #endif
1026 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */