[en_US] Added 13 autocorrect words
[LibreOffice.git] / compilerplugins / clang / unusedfields.cxx
blob98c5a30d6a1d8bae96133cd9e087dcb917f555a5
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 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
30 #endif
32 /**
33 This performs two analyses:
34 (1) look for unused fields
35 (2) look for fields that are write-only
37 We dmp a list of calls to methods, and a list of field definitions.
38 Then we will post-process the 2 lists and find the set of unused methods.
40 Be warned that it produces around 5G of log file.
42 The process goes something like this:
43 $ make check
44 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedfields' check
45 $ ./compilerplugins/clang/unusedfields.py
47 and then
48 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done
49 to auto-remove the method declarations
51 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
52 to get it to work :-)
56 namespace {
58 struct MyFieldInfo
60 const RecordDecl* parentRecord;
61 std::string parentClass;
62 std::string fieldName;
63 std::string fieldType;
64 std::string sourceLocation;
65 std::string access;
67 bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
69 return std::tie(lhs.parentClass, lhs.fieldName)
70 < std::tie(rhs.parentClass, rhs.fieldName);
74 // try to limit the voluminous output a little
75 static std::set<MyFieldInfo> touchedFromInsideSet;
76 static std::set<MyFieldInfo> touchedFromOutsideSet;
77 static std::set<MyFieldInfo> touchedFromOutsideConstructorSet;
78 static std::set<MyFieldInfo> readFromSet;
79 static std::set<MyFieldInfo> writeToSet;
80 static std::set<MyFieldInfo> definitionSet;
82 /**
83 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
85 class CallerWrapper
87 const CallExpr * m_callExpr;
88 const CXXConstructExpr * m_cxxConstructExpr;
89 public:
90 CallerWrapper(const CallExpr * callExpr) : m_callExpr(callExpr), m_cxxConstructExpr(nullptr) {}
91 CallerWrapper(const CXXConstructExpr * cxxConstructExpr) : m_callExpr(nullptr), m_cxxConstructExpr(cxxConstructExpr) {}
92 unsigned getNumArgs () const
93 { return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs(); }
94 const Expr * getArg (unsigned i) const
95 { return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i); }
97 class CalleeWrapper
99 const FunctionDecl * m_calleeFunctionDecl = nullptr;
100 const CXXConstructorDecl * m_cxxConstructorDecl = nullptr;
101 const FunctionProtoType * m_functionPrototype = nullptr;
102 public:
103 explicit CalleeWrapper(const FunctionDecl * calleeFunctionDecl) : m_calleeFunctionDecl(calleeFunctionDecl) {}
104 explicit CalleeWrapper(const CXXConstructExpr * cxxConstructExpr) : m_cxxConstructorDecl(cxxConstructExpr->getConstructor()) {}
105 explicit CalleeWrapper(const FunctionProtoType * functionPrototype) : m_functionPrototype(functionPrototype) {}
106 unsigned getNumParams() const
108 if (m_calleeFunctionDecl)
109 return m_calleeFunctionDecl->getNumParams();
110 else if (m_cxxConstructorDecl)
111 return m_cxxConstructorDecl->getNumParams();
112 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
113 // FunctionProtoType will assert if we call getParamTypes() and it has no params
114 return 0;
115 else
116 return m_functionPrototype->getParamTypes().size();
118 const QualType getParamType(unsigned i) const
120 if (m_calleeFunctionDecl)
121 return m_calleeFunctionDecl->getParamDecl(i)->getType();
122 else if (m_cxxConstructorDecl)
123 return m_cxxConstructorDecl->getParamDecl(i)->getType();
124 else
125 return m_functionPrototype->getParamTypes()[i];
127 std::string getNameAsString() const
129 if (m_calleeFunctionDecl)
130 return m_calleeFunctionDecl->getNameAsString();
131 else if (m_cxxConstructorDecl)
132 return m_cxxConstructorDecl->getNameAsString();
133 else
134 return "";
136 CXXMethodDecl const * getAsCXXMethodDecl() const
138 if (m_calleeFunctionDecl)
139 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
140 return nullptr;
144 class UnusedFields:
145 public loplugin::FilteringPlugin<UnusedFields>
147 public:
148 explicit UnusedFields(loplugin::InstantiationData const & data):
149 FilteringPlugin(data) {}
151 virtual void run() override;
153 bool shouldVisitTemplateInstantiations () const { return true; }
154 bool shouldVisitImplicitCode() const { return true; }
156 bool VisitFieldDecl( const FieldDecl* );
157 bool VisitMemberExpr( const MemberExpr* );
158 bool VisitDeclRefExpr( const DeclRefExpr* );
159 bool VisitCXXConstructorDecl( const CXXConstructorDecl* );
160 bool VisitInitListExpr( const InitListExpr* );
161 bool TraverseCXXConstructorDecl( CXXConstructorDecl* );
162 bool TraverseCXXMethodDecl( CXXMethodDecl* );
163 bool TraverseFunctionDecl( FunctionDecl* );
164 bool TraverseIfStmt( IfStmt* );
166 private:
167 MyFieldInfo niceName(const FieldDecl*);
168 void checkTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr);
169 void checkIfReadFrom(const FieldDecl* fieldDecl, const Expr* memberExpr);
170 void checkIfWrittenTo(const FieldDecl* fieldDecl, const Expr* memberExpr);
171 bool isSomeKindOfZero(const Expr* arg);
172 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl);
173 bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt * child, CallerWrapper callExpr,
174 CalleeWrapper calleeFunctionDecl);
175 llvm::Optional<CalleeWrapper> getCallee(CallExpr const *);
177 RecordDecl * insideMoveOrCopyOrCloneDeclParent = nullptr;
178 RecordDecl * insideStreamOutputOperator = nullptr;
179 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
180 // we store the parent function on the way down the AST.
181 FunctionDecl * insideFunctionDecl = nullptr;
182 std::vector<FieldDecl const *> insideConditionalCheckOfMemberSet;
185 void UnusedFields::run()
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 compat::getBeginLoc(s.parentRecord))
218 << s.fieldName;
219 for (const MyFieldInfo & s : writeToSet)
220 report(
221 DiagnosticsEngine::Warning,
222 "write %0",
223 compat::getBeginLoc(s.parentRecord))
224 << s.fieldName;
229 MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
231 MyFieldInfo aInfo;
233 const RecordDecl* recordDecl = fieldDecl->getParent();
235 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
237 if (cxxRecordDecl->getTemplateInstantiationPattern())
238 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
239 aInfo.parentRecord = cxxRecordDecl;
240 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
242 else
244 aInfo.parentRecord = recordDecl;
245 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
248 aInfo.fieldName = fieldDecl->getNameAsString();
249 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
250 size_t idx = aInfo.fieldName.find(SRCDIR);
251 if (idx != std::string::npos) {
252 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
254 aInfo.fieldType = fieldDecl->getType().getAsString();
256 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldDecl->getLocation() );
257 StringRef name = getFilenameOfLocation(expansionLoc);
258 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
259 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
261 switch (fieldDecl->getAccess())
263 case AS_public: aInfo.access = "public"; break;
264 case AS_private: aInfo.access = "private"; break;
265 case AS_protected: aInfo.access = "protected"; break;
266 default: aInfo.access = "unknown"; break;
269 return aInfo;
272 bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
274 fieldDecl = fieldDecl->getCanonicalDecl();
275 if (ignoreLocation( fieldDecl )) {
276 return true;
278 // ignore stuff that forms part of the stable URE interface
279 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
280 return true;
283 if (fieldDecl->getInClassInitializer() && !isSomeKindOfZero(fieldDecl->getInClassInitializer())) {
284 writeToSet.insert(niceName(fieldDecl));
287 definitionSet.insert(niceName(fieldDecl));
288 return true;
292 Does the expression being used to initialise a field value evaluate to
293 the same as a default value?
295 bool UnusedFields::isSomeKindOfZero(const Expr* arg)
297 assert(arg);
298 arg = arg->IgnoreParenCasts();
299 if (isa<CXXDefaultArgExpr>(arg)) {
300 arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
302 arg = arg->IgnoreParenCasts();
303 // ignore this, it seems to trigger an infinite recursion
304 if (isa<UnaryExprOrTypeTraitExpr>(arg)) {
305 return false;
307 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg)) {
308 return cxxConstructExpr->getConstructor()->isDefaultConstructor();
310 APSInt x1;
311 if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
313 return x1 == 0;
315 if (isa<CXXNullPtrLiteralExpr>(arg)) {
316 return true;
318 if (isa<MaterializeTemporaryExpr>(arg))
320 const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
321 if (strippedArg)
323 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
324 if (temp->getNumArgs() == 0)
326 if (loplugin::TypeCheck(temp->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
327 return true;
329 if (loplugin::TypeCheck(temp->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
330 return true;
332 return false;
337 // Get the expression contents.
338 // This helps us find params which are always initialised with something like "OUString()".
339 SourceManager& SM = compiler.getSourceManager();
340 SourceLocation startLoc = compat::getBeginLoc(arg);
341 SourceLocation endLoc = compat::getEndLoc(arg);
342 const char *p1 = SM.getCharacterData( startLoc );
343 const char *p2 = SM.getCharacterData( endLoc );
344 if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40) {
345 return false;
347 unsigned n = Lexer::MeasureTokenLength( endLoc, SM, compiler.getLangOpts());
348 std::string s( p1, p2 - p1 + n);
349 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
350 std::replace( s.begin(), s.end(), '\r', ' ');
351 std::replace( s.begin(), s.end(), '\n', ' ');
352 std::replace( s.begin(), s.end(), '\t', ' ');
354 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
355 if (s == "OUString()")
356 return true;
357 else if (s == "OString()")
358 return true;
359 else if (s == "aEmptyOUStr") //sw
360 return true;
361 else if (s == "EMPTY_OUSTRING")//sc
362 return true;
363 else if (s == "GetEmptyOUString()") //sc
364 return true;
365 return false;
368 static char easytolower(char in)
370 if (in<='Z' && in>='A')
371 return in-('Z'-'z');
372 return in;
375 bool startswith(const std::string& rStr, const char* pSubStr)
377 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
380 bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
382 auto copy = insideMoveOrCopyOrCloneDeclParent;
383 if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
385 if (cxxConstructorDecl->isCopyOrMoveConstructor())
386 insideMoveOrCopyOrCloneDeclParent = cxxConstructorDecl->getParent();
388 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
389 insideMoveOrCopyOrCloneDeclParent = copy;
390 return ret;
393 bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
395 auto copy1 = insideMoveOrCopyOrCloneDeclParent;
396 auto copy2 = insideFunctionDecl;
397 if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
399 if (cxxMethodDecl->isCopyAssignmentOperator()
400 || cxxMethodDecl->isMoveAssignmentOperator()
401 || (cxxMethodDecl->getIdentifier()
402 && (cxxMethodDecl->getName().startswith("Clone")
403 || cxxMethodDecl->getName().startswith("clone")
404 || cxxMethodDecl->getName().startswith("createClone"))))
405 insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent();
406 // these are similar in that they tend to simply enumerate all the fields of an object without putting
407 // them to some useful purpose
408 auto op = cxxMethodDecl->getOverloadedOperator();
409 if (op == OO_EqualEqual || op == OO_ExclaimEqual)
410 insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent();
412 insideFunctionDecl = cxxMethodDecl;
413 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
414 insideMoveOrCopyOrCloneDeclParent = copy1;
415 insideFunctionDecl = copy2;
416 return ret;
419 bool UnusedFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
421 auto copy1 = insideStreamOutputOperator;
422 auto copy2 = insideFunctionDecl;
423 auto copy3 = insideMoveOrCopyOrCloneDeclParent;
424 if (functionDecl->getLocation().isValid() && !ignoreLocation(functionDecl) && functionDecl->isThisDeclarationADefinition())
426 auto op = functionDecl->getOverloadedOperator();
427 if (op == OO_LessLess
428 && functionDecl->getNumParams() == 2)
430 QualType qt = functionDecl->getParamDecl(1)->getType();
431 insideStreamOutputOperator = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
433 // these are similar in that they tend to simply enumerate all the fields of an object without putting
434 // them to some useful purpose
435 if (op == OO_EqualEqual || op == OO_ExclaimEqual)
437 QualType qt = functionDecl->getParamDecl(1)->getType();
438 insideMoveOrCopyOrCloneDeclParent = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
441 insideFunctionDecl = functionDecl;
442 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
443 insideStreamOutputOperator = copy1;
444 insideFunctionDecl = copy2;
445 insideMoveOrCopyOrCloneDeclParent = copy3;
446 return ret;
449 bool UnusedFields::TraverseIfStmt(IfStmt* ifStmt)
451 FieldDecl const * memberFieldDecl = nullptr;
452 Expr const * cond = ifStmt->getCond()->IgnoreParenImpCasts();
454 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(cond))
456 if (auto cxxConvert = dyn_cast_or_null<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
458 if (cxxConvert->getConversionType()->isBooleanType())
459 if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
460 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
461 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
463 else if (auto cxxMethod = memberCallExpr->getMethodDecl())
465 if (cxxMethod->getIdentifier() && cxxMethod->getName() == "get" && memberCallExpr->getNumArgs()==0)
466 if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
467 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
468 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
471 else if (auto memberExpr = dyn_cast<MemberExpr>(cond))
473 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
474 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
477 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
478 if (memberFieldDecl)
479 insideConditionalCheckOfMemberSet.pop_back();
480 return ret;
483 bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
485 const ValueDecl* decl = memberExpr->getMemberDecl();
486 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
487 if (!fieldDecl) {
488 return true;
490 fieldDecl = fieldDecl->getCanonicalDecl();
491 if (ignoreLocation(fieldDecl)) {
492 return true;
494 // ignore stuff that forms part of the stable URE interface
495 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
496 return true;
499 checkTouchedFromOutside(fieldDecl, memberExpr);
501 checkIfReadFrom(fieldDecl, memberExpr);
503 checkIfWrittenTo(fieldDecl, memberExpr);
505 return true;
508 void UnusedFields::checkIfReadFrom(const FieldDecl* fieldDecl, const Expr* memberExpr)
510 if (insideMoveOrCopyOrCloneDeclParent || insideStreamOutputOperator)
512 RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent();
513 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
514 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent))
515 return;
516 // we don't care about reads when the field is being used in an output operator, this is normally
517 // debug stuff
518 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator))
519 return;
522 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
523 const Stmt* child = memberExpr;
524 const Stmt* parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
525 // walk up the tree until we find something interesting
526 bool bPotentiallyReadFrom = false;
527 bool bDump = false;
528 auto walkUp = [&]() {
529 child = parent;
530 auto parentsRange = compiler.getASTContext().getParents(*parent);
531 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
535 if (!parent)
537 // check if we're inside a CXXCtorInitializer or a VarDecl
538 auto parentsRange = compiler.getASTContext().getParents(*child);
539 if ( parentsRange.begin() != parentsRange.end())
541 const Decl* decl = parentsRange.begin()->get<Decl>();
542 if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
543 bPotentiallyReadFrom = true;
545 if (!bPotentiallyReadFrom)
546 return;
547 break;
549 if (isa<CXXReinterpretCastExpr>(parent))
551 // once we see one of these, there is not much useful we can know
552 bPotentiallyReadFrom = true;
553 break;
555 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
556 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
558 walkUp();
560 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
562 UnaryOperator::Opcode op = unaryOperator->getOpcode();
563 if (memberExpr->getType()->isArrayType() && op == UO_Deref)
565 // ignore, deref'ing an array does not count as a read
567 else if (op == UO_AddrOf || op == UO_Deref
568 || op == UO_Plus || op == UO_Minus
569 || op == UO_Not || op == UO_LNot)
571 bPotentiallyReadFrom = true;
572 break;
574 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
575 ignore them to find interesting fields that only modified, not usefully read:
576 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
577 But we still walk up in case the result of the expression is used in a read sense.
579 walkUp();
581 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
583 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
584 break;
586 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
588 bPotentiallyReadFrom = ifStmt->getCond() == child;
589 break;
591 else if (auto doStmt = dyn_cast<DoStmt>(parent))
593 bPotentiallyReadFrom = doStmt->getCond() == child;
594 break;
596 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
598 if (arraySubscriptExpr->getIdx() == child)
600 bPotentiallyReadFrom = true;
601 break;
603 walkUp();
605 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
607 BinaryOperator::Opcode op = binaryOp->getOpcode();
608 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign
609 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
610 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
611 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign;
612 if (binaryOp->getLHS() == child && assignmentOp)
613 break;
614 else
616 bPotentiallyReadFrom = true;
617 break;
620 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
622 auto op = operatorCallExpr->getOperator();
623 const bool assignmentOp = op == OO_Equal || op == OO_StarEqual ||
624 op == OO_SlashEqual || op == OO_PercentEqual ||
625 op == OO_PlusEqual || op == OO_MinusEqual ||
626 op == OO_LessLessEqual ||
627 op == OO_AmpEqual || op == OO_CaretEqual ||
628 op == OO_PipeEqual;
629 if (operatorCallExpr->getArg(0) == child && assignmentOp)
630 break;
631 else if (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child)
632 break; // this is a write-only call
633 else
635 bPotentiallyReadFrom = true;
636 break;
639 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
641 bool bWriteOnlyCall = false;
642 const CXXMethodDecl * callee = cxxMemberCallExpr->getMethodDecl();
643 if (callee)
645 const Expr* tmp = dyn_cast<Expr>(child);
646 if (tmp->isBoundMemberFunction(compiler.getASTContext())) {
647 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
649 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
651 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
652 // which we could scatter around.
653 std::string name = callee->getNameAsString();
654 std::transform(name.begin(), name.end(), name.begin(), easytolower);
655 if (startswith(name, "emplace") || name == "insert"
656 || name == "erase" || name == "remove" || name == "remove_if" || name == "sort"
657 || name == "push_back" || name == "pop_back"
658 || name == "push_front" || name == "pop_front"
659 || name == "reserve" || name == "resize" || name == "reset"
660 || name == "clear" || name == "fill")
661 // write-only modifications to collections
662 bWriteOnlyCall = true;
663 else if (name == "dispose" || name == "disposeAndClear" || name == "swap")
664 // we're abusing the write-only analysis here to look for fields which don't have anything useful
665 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
666 // and VclPtr::disposeAndClear
667 bWriteOnlyCall = true;
670 if (!bWriteOnlyCall)
671 bPotentiallyReadFrom = true;
672 break;
674 else if (auto callExpr = dyn_cast<CallExpr>(parent))
676 bool bWriteOnlyCall = false;
677 // check for calls to ReadXXX(foo) type methods, where foo is write-only
678 auto callee = getCallee(callExpr);
679 if (callee)
681 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
682 // which we could scatter around.
683 std::string name = callee->getNameAsString();
684 std::transform(name.begin(), name.end(), name.begin(), easytolower);
685 if (startswith(name, "read"))
686 // this is a write-only call
687 bWriteOnlyCall = true;
689 if (!bWriteOnlyCall)
690 bPotentiallyReadFrom = true;
691 break;
693 else if (isa<ReturnStmt>(parent)
694 || isa<CXXConstructExpr>(parent)
695 || isa<ConditionalOperator>(parent)
696 || isa<SwitchStmt>(parent)
697 || isa<DeclStmt>(parent)
698 || isa<WhileStmt>(parent)
699 || isa<CXXNewExpr>(parent)
700 || isa<ForStmt>(parent)
701 || isa<InitListExpr>(parent)
702 || isa<CXXDependentScopeMemberExpr>(parent)
703 || isa<UnresolvedMemberExpr>(parent)
704 || isa<MaterializeTemporaryExpr>(parent))
706 bPotentiallyReadFrom = true;
707 break;
709 else if (isa<CXXDeleteExpr>(parent)
710 || isa<UnaryExprOrTypeTraitExpr>(parent)
711 || isa<CXXUnresolvedConstructExpr>(parent)
712 || isa<CompoundStmt>(parent)
713 || isa<LabelStmt>(parent)
714 || isa<CXXForRangeStmt>(parent)
715 || isa<CXXTypeidExpr>(parent)
716 || isa<DefaultStmt>(parent))
718 break;
720 else
722 bPotentiallyReadFrom = true;
723 bDump = true;
724 break;
726 } while (true);
728 if (bDump)
730 report(
731 DiagnosticsEngine::Warning,
732 "oh dear, what can the matter be?",
733 compat::getBeginLoc(memberExpr))
734 << memberExpr->getSourceRange();
735 report(
736 DiagnosticsEngine::Note,
737 "parent over here",
738 compat::getBeginLoc(parent))
739 << parent->getSourceRange();
740 parent->dump();
741 memberExpr->dump();
744 MyFieldInfo fieldInfo = niceName(fieldDecl);
745 if (bPotentiallyReadFrom)
747 readFromSet.insert(fieldInfo);
751 void UnusedFields::checkIfWrittenTo(const FieldDecl* fieldDecl, const Expr* memberExpr)
753 if (insideMoveOrCopyOrCloneDeclParent)
755 RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent();
756 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
757 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent))
759 return;
763 // if we're inside a block that looks like
764 // if (fieldDecl)
765 // ...
766 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
767 if (std::find(insideConditionalCheckOfMemberSet.begin(), insideConditionalCheckOfMemberSet.end(), fieldDecl) != insideConditionalCheckOfMemberSet.end())
768 return;
770 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
771 const Stmt* child = memberExpr;
772 const Stmt* parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
773 // walk up the tree until we find something interesting
774 bool bPotentiallyWrittenTo = false;
775 bool bDump = false;
776 auto walkUp = [&]() {
777 child = parent;
778 auto parentsRange = compiler.getASTContext().getParents(*parent);
779 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
783 if (!parent)
785 // check if we have an expression like
786 // int& r = m_field;
787 auto parentsRange = compiler.getASTContext().getParents(*child);
788 if (parentsRange.begin() != parentsRange.end())
790 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
791 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
792 // which is of type 'T&&' and also an l-value-ref ?
793 if (varDecl && !varDecl->isImplicit() && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
795 bPotentiallyWrittenTo = true;
798 break;
800 if (isa<CXXReinterpretCastExpr>(parent))
802 // once we see one of these, there is not much useful we can know
803 bPotentiallyWrittenTo = true;
804 break;
806 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
807 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
809 walkUp();
811 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
813 UnaryOperator::Opcode op = unaryOperator->getOpcode();
814 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc || op == UO_PreDec)
816 bPotentiallyWrittenTo = true;
818 break;
820 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
822 if (arraySubscriptExpr->getIdx() == child)
823 break;
824 walkUp();
826 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
828 auto callee = getCallee(operatorCallExpr);
829 if (callee)
831 // if calling a non-const operator on the field
832 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
833 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
835 if (!calleeMethodDecl->isConst())
836 bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
838 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
840 bPotentiallyWrittenTo = true;
843 else
844 bPotentiallyWrittenTo = true; // conservative, could improve
845 break;
847 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
849 const CXXMethodDecl * calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
850 if (calleeMethodDecl)
852 // if calling a non-const method on the field
853 const Expr* tmp = dyn_cast<Expr>(child);
854 if (tmp->isBoundMemberFunction(compiler.getASTContext())) {
855 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
857 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
859 if (!calleeMethodDecl->isConst())
860 bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
861 break;
863 else if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr, CalleeWrapper(calleeMethodDecl)))
864 bPotentiallyWrittenTo = true;
866 else
867 bPotentiallyWrittenTo = true; // can happen in templates
868 break;
870 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
872 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr, CalleeWrapper(cxxConstructExpr)))
873 bPotentiallyWrittenTo = true;
874 break;
876 else if (auto callExpr = dyn_cast<CallExpr>(parent))
878 auto callee = getCallee(callExpr);
879 if (callee) {
880 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
881 bPotentiallyWrittenTo = true;
882 } else
883 bPotentiallyWrittenTo = true; // conservative, could improve
884 break;
886 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
888 BinaryOperator::Opcode op = binaryOp->getOpcode();
889 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign
890 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
891 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
892 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign;
893 if (assignmentOp)
895 if (binaryOp->getLHS() == child)
896 bPotentiallyWrittenTo = true;
897 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType()).LvalueReference().NonConst())
898 // if the LHS is a non-const reference, we could write to the field later on
899 bPotentiallyWrittenTo = true;
901 break;
903 else if (isa<ReturnStmt>(parent))
905 if (insideFunctionDecl)
907 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
908 if (tc.LvalueReference().NonConst())
909 bPotentiallyWrittenTo = true;
911 break;
913 else if (isa<ConditionalOperator>(parent)
914 || isa<SwitchStmt>(parent)
915 || isa<DeclStmt>(parent)
916 || isa<WhileStmt>(parent)
917 || isa<CXXNewExpr>(parent)
918 || isa<ForStmt>(parent)
919 || isa<InitListExpr>(parent)
920 || isa<CXXDependentScopeMemberExpr>(parent)
921 || isa<UnresolvedMemberExpr>(parent)
922 || isa<MaterializeTemporaryExpr>(parent)
923 || isa<IfStmt>(parent)
924 || isa<DoStmt>(parent)
925 || isa<CXXDeleteExpr>(parent)
926 || isa<UnaryExprOrTypeTraitExpr>(parent)
927 || isa<CXXUnresolvedConstructExpr>(parent)
928 || isa<CompoundStmt>(parent)
929 || isa<LabelStmt>(parent)
930 || isa<CXXForRangeStmt>(parent)
931 || isa<CXXTypeidExpr>(parent)
932 || isa<DefaultStmt>(parent))
934 break;
936 else
938 bPotentiallyWrittenTo = true;
939 bDump = true;
940 break;
942 } while (true);
944 if (bDump)
946 report(
947 DiagnosticsEngine::Warning,
948 "oh dear, what can the matter be? writtenTo=%0",
949 compat::getBeginLoc(memberExpr))
950 << bPotentiallyWrittenTo
951 << memberExpr->getSourceRange();
952 if (parent)
954 report(
955 DiagnosticsEngine::Note,
956 "parent over here",
957 compat::getBeginLoc(parent))
958 << parent->getSourceRange();
959 parent->dump();
961 memberExpr->dump();
962 fieldDecl->getType()->dump();
965 MyFieldInfo fieldInfo = niceName(fieldDecl);
966 if (bPotentiallyWrittenTo)
968 writeToSet.insert(fieldInfo);
972 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
973 bool UnusedFields::checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl)
975 auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
976 bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
977 if (tc.Class("deque").StdNamespace()
978 || tc.Class("list").StdNamespace()
979 || tc.Class("queue").StdNamespace()
980 || tc.Class("vector").StdNamespace())
982 listLike = true;
984 else if (tc.Class("set").StdNamespace()
985 || tc.Class("unordered_set").StdNamespace())
987 setLike = true;
989 else if (tc.Class("map").StdNamespace()
990 || tc.Class("unordered_map").StdNamespace())
992 mapLike = true;
994 else if (tc.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
996 cssSequence = true;
998 else
999 return true;
1001 if (calleeMethodDecl->isOverloadedOperator())
1003 auto oo = calleeMethodDecl->getOverloadedOperator();
1004 if (oo == OO_Equal)
1005 return true;
1006 // This is operator[]. We only care about things that add elements to the collection.
1007 // if nothing modifies the size of the collection, then nothing useful
1008 // is stored in it.
1009 if (listLike)
1010 return false;
1011 return true;
1014 auto name = calleeMethodDecl->getName();
1015 if (listLike || setLike || mapLike)
1017 if (name == "reserve" || name == "shrink_to_fit" || name == "clear"
1018 || name == "erase" || name == "pop_back" || name == "pop_front"
1019 || name == "front" || name == "back" || name == "data"
1020 || name == "remove" || name == "remove_if"
1021 || name == "unique" || name == "sort"
1022 || name == "begin" || name == "end"
1023 || name == "rbegin" || name == "rend"
1024 || name == "at" || name == "find" || name == "equal_range"
1025 || name == "lower_bound" || name == "upper_bound")
1026 return false;
1028 if (cssSequence)
1030 if (name == "getArray" || name == "begin" || name == "end")
1031 return false;
1034 return true;
1037 bool UnusedFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt * child, CallerWrapper callExpr,
1038 CalleeWrapper calleeFunctionDecl)
1040 unsigned len = std::min(callExpr.getNumArgs(),
1041 calleeFunctionDecl.getNumParams());
1042 // if it's an array, passing it by value to a method typically means the
1043 // callee takes a pointer and can modify the array
1044 if (fieldDecl->getType()->isConstantArrayType())
1046 for (unsigned i = 0; i < len; ++i)
1047 if (callExpr.getArg(i) == child)
1048 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
1049 return true;
1051 else
1053 for (unsigned i = 0; i < len; ++i)
1054 if (callExpr.getArg(i) == child)
1055 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).LvalueReference().NonConst())
1056 return true;
1058 return false;
1061 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
1062 // have to do it here
1063 bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl* cxxConstructorDecl )
1065 if (ignoreLocation( cxxConstructorDecl )) {
1066 return true;
1068 // ignore stuff that forms part of the stable URE interface
1069 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation()))) {
1070 return true;
1073 // templates make EvaluateAsInt crash inside clang
1074 if (cxxConstructorDecl->isDependentContext())
1075 return true;
1077 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
1078 if (insideMoveOrCopyOrCloneDeclParent && cxxConstructorDecl->getParent() == insideMoveOrCopyOrCloneDeclParent)
1079 return true;
1081 for(auto it = cxxConstructorDecl->init_begin(); it != cxxConstructorDecl->init_end(); ++it)
1083 const CXXCtorInitializer* init = *it;
1084 const FieldDecl* fieldDecl = init->getMember();
1085 if (fieldDecl && init->getInit() && !isSomeKindOfZero(init->getInit()))
1087 MyFieldInfo fieldInfo = niceName(fieldDecl);
1088 writeToSet.insert(fieldInfo);
1091 return true;
1094 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
1095 // have to do it here.
1096 bool UnusedFields::VisitInitListExpr( const InitListExpr* initListExpr)
1098 if (ignoreLocation( initListExpr ))
1099 return true;
1101 QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext());
1102 auto recordType = varType->getAs<RecordType>();
1103 if (!recordType)
1104 return true;
1106 auto recordDecl = recordType->getDecl();
1107 for (auto it = recordDecl->field_begin(); it != recordDecl->field_end(); ++it)
1109 MyFieldInfo fieldInfo = niceName(*it);
1110 writeToSet.insert(fieldInfo);
1113 return true;
1116 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
1118 const Decl* decl = declRefExpr->getDecl();
1119 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
1120 if (!fieldDecl) {
1121 return true;
1123 fieldDecl = fieldDecl->getCanonicalDecl();
1124 if (ignoreLocation(fieldDecl)) {
1125 return true;
1127 // ignore stuff that forms part of the stable URE interface
1128 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
1129 return true;
1131 checkTouchedFromOutside(fieldDecl, declRefExpr);
1132 return true;
1135 void UnusedFields::checkTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr) {
1136 const FunctionDecl* memberExprParentFunction = getParentFunctionDecl(memberExpr);
1137 const CXXMethodDecl* methodDecl = dyn_cast_or_null<CXXMethodDecl>(memberExprParentFunction);
1139 MyFieldInfo fieldInfo = niceName(fieldDecl);
1141 // it's touched from somewhere outside a class
1142 if (!methodDecl) {
1143 touchedFromOutsideSet.insert(fieldInfo);
1144 return;
1147 auto constructorDecl = dyn_cast<CXXConstructorDecl>(methodDecl);
1148 if (methodDecl->isCopyAssignmentOperator() || methodDecl->isMoveAssignmentOperator()) {
1149 // ignore move/copy operator, it's self->self
1150 } else if (constructorDecl && (constructorDecl->isCopyConstructor() || constructorDecl->isMoveConstructor())) {
1151 // ignore move/copy constructor, it's self->self
1152 } else {
1153 if (memberExprParentFunction->getParent() == fieldDecl->getParent()) {
1154 touchedFromInsideSet.insert(fieldInfo);
1155 if (!constructorDecl)
1156 touchedFromOutsideConstructorSet.insert(fieldInfo);
1157 } else {
1158 touchedFromOutsideSet.insert(fieldInfo);
1163 llvm::Optional<CalleeWrapper> UnusedFields::getCallee(CallExpr const * callExpr)
1165 FunctionDecl const * functionDecl = callExpr->getDirectCallee();
1166 if (functionDecl)
1167 return CalleeWrapper(functionDecl);
1169 // Extract the functionprototype from a type
1170 clang::Type const * calleeType = callExpr->getCallee()->getType().getTypePtr();
1171 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>()) {
1172 if (auto prototype = pointerType->getPointeeType()->getUnqualifiedDesugaredType()->getAs<FunctionProtoType>()) {
1173 return CalleeWrapper(prototype);
1177 return llvm::Optional<CalleeWrapper>();
1180 loplugin::Plugin::Registration< UnusedFields > X("unusedfields", false);
1184 #endif
1186 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */