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