lok: getSlideShowInfo: interactions: check that properties are available
[LibreOffice.git] / compilerplugins / clang / unusedvarsglobal.cxx
blobf74bfedb6c0d2397b72f701a11250ba22006e06d
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 global vars
33 (2) look for global vars that are write-only
36 namespace
38 struct MyVarInfo
40 const VarDecl* varDecl;
41 std::string fieldName;
42 std::string fieldType;
43 std::string sourceLocation;
45 bool operator<(const MyVarInfo& lhs, const MyVarInfo& rhs)
47 return std::tie(lhs.sourceLocation, lhs.fieldName)
48 < std::tie(rhs.sourceLocation, rhs.fieldName);
51 // try to limit the voluminous output a little
52 static std::set<MyVarInfo> readFromSet;
53 static std::set<MyVarInfo> writeToSet;
54 static std::set<MyVarInfo> definitionSet;
56 /**
57 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
59 class CallerWrapper
61 const CallExpr* m_callExpr;
62 const CXXConstructExpr* m_cxxConstructExpr;
64 public:
65 CallerWrapper(const CallExpr* callExpr)
66 : m_callExpr(callExpr)
67 , m_cxxConstructExpr(nullptr)
70 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
71 : m_callExpr(nullptr)
72 , m_cxxConstructExpr(cxxConstructExpr)
75 unsigned getNumArgs() const
77 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
79 const Expr* getArg(unsigned i) const
81 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
84 class CalleeWrapper
86 const FunctionDecl* m_calleeFunctionDecl = nullptr;
87 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
88 const FunctionProtoType* m_functionPrototype = nullptr;
90 public:
91 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
92 : m_calleeFunctionDecl(calleeFunctionDecl)
95 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
96 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
99 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
100 : m_functionPrototype(functionPrototype)
103 unsigned getNumParams() const
105 if (m_calleeFunctionDecl)
106 return m_calleeFunctionDecl->getNumParams();
107 else if (m_cxxConstructorDecl)
108 return m_cxxConstructorDecl->getNumParams();
109 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
110 // FunctionProtoType will assert if we call getParamTypes() and it has no params
111 return 0;
112 else
113 return m_functionPrototype->getParamTypes().size();
115 const QualType getParamType(unsigned i) const
117 if (m_calleeFunctionDecl)
118 return m_calleeFunctionDecl->getParamDecl(i)->getType();
119 else if (m_cxxConstructorDecl)
120 return m_cxxConstructorDecl->getParamDecl(i)->getType();
121 else
122 return m_functionPrototype->getParamTypes()[i];
124 std::string getNameAsString() const
126 if (m_calleeFunctionDecl)
127 return m_calleeFunctionDecl->getNameAsString();
128 else if (m_cxxConstructorDecl)
129 return m_cxxConstructorDecl->getNameAsString();
130 else
131 return "";
133 CXXMethodDecl const* getAsCXXMethodDecl() const
135 if (m_calleeFunctionDecl)
136 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
137 return nullptr;
141 class UnusedVarsGlobal : public loplugin::FilteringPlugin<UnusedVarsGlobal>
143 public:
144 explicit UnusedVarsGlobal(loplugin::InstantiationData const& data)
145 : FilteringPlugin(data)
149 virtual void run() override;
151 bool shouldVisitTemplateInstantiations() const { return true; }
152 bool shouldVisitImplicitCode() const { return true; }
154 bool VisitVarDecl(const VarDecl*);
155 bool VisitDeclRefExpr(const DeclRefExpr*);
156 bool TraverseCXXMethodDecl(CXXMethodDecl*);
157 bool TraverseFunctionDecl(FunctionDecl*);
158 bool TraverseIfStmt(IfStmt*);
160 private:
161 MyVarInfo niceName(const VarDecl*);
162 void checkIfReadFrom(const VarDecl* fieldDecl, const DeclRefExpr* declRefExpr);
163 void checkIfWrittenTo(const VarDecl* fieldDecl, const DeclRefExpr* declRefExpr);
164 bool isSomeKindOfZero(const Expr* arg);
165 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl);
166 bool IsPassedByNonConst(const VarDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
167 CalleeWrapper calleeFunctionDecl);
168 compat::optional<CalleeWrapper> getCallee(CallExpr const*);
170 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
171 // we store the parent function on the way down the AST.
172 FunctionDecl* insideFunctionDecl = nullptr;
173 std::vector<VarDecl const*> insideConditionalCheckOfVarSet;
176 void UnusedVarsGlobal::run()
178 handler.enableTreeWideAnalysisMode();
180 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
182 if (!isUnitTestMode())
184 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
185 // writing to the same logfile
186 std::string output;
187 for (const MyVarInfo& s : readFromSet)
188 output += "read:\t" + s.sourceLocation + "\t" + s.fieldName + "\n";
189 for (const MyVarInfo& s : writeToSet)
190 output += "write:\t" + s.sourceLocation + "\t" + s.fieldName + "\n";
191 for (const MyVarInfo& s : definitionSet)
192 output += "definition:\t" + s.fieldName + "\t" + s.fieldType + "\t" + s.sourceLocation
193 + "\n";
194 std::ofstream myfile;
195 myfile.open(WORKDIR "/loplugin.unusedvarsglobal.log", std::ios::app | std::ios::out);
196 myfile << output;
197 myfile.close();
199 else
201 for (const MyVarInfo& s : readFromSet)
202 report(DiagnosticsEngine::Warning, "read", s.varDecl->getBeginLoc());
203 for (const MyVarInfo& s : writeToSet)
204 report(DiagnosticsEngine::Warning, "write", s.varDecl->getBeginLoc());
208 MyVarInfo UnusedVarsGlobal::niceName(const VarDecl* varDecl)
210 MyVarInfo aInfo;
211 aInfo.varDecl = varDecl;
213 aInfo.fieldName = varDecl->getNameAsString();
214 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
215 size_t idx = aInfo.fieldName.find(SRCDIR);
216 if (idx != std::string::npos)
218 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
220 aInfo.fieldType = varDecl->getType().getAsString();
222 SourceLocation expansionLoc
223 = compiler.getSourceManager().getExpansionLoc(varDecl->getLocation());
224 StringRef name = getFilenameOfLocation(expansionLoc);
225 aInfo.sourceLocation
226 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
227 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
228 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
230 return aInfo;
233 bool UnusedVarsGlobal::VisitVarDecl(const VarDecl* varDecl)
235 varDecl = varDecl->getCanonicalDecl();
236 if (isa<ParmVarDecl>(varDecl))
237 return true;
238 if (!varDecl->hasGlobalStorage())
239 return true;
240 if (!varDecl->getLocation().isValid() || ignoreLocation(varDecl))
241 return true;
242 // ignore stuff that forms part of the stable URE interface
243 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
244 return true;
247 If we have
248 const size_t NB_PRODUCTS = 3;
249 int DefaultProductDir[NB_PRODUCTS] = { 3, 3, 3 };
250 clang will inline the constant "3" and never tell us that we are reading from NB_PRODUCTS,
251 so just ignore integer constants.
253 auto varType = varDecl->getType();
254 if (varType.isConstQualified() && varType->isIntegerType())
255 return true;
257 auto initExpr = varDecl->getAnyInitializer();
258 if (initExpr && !isSomeKindOfZero(initExpr))
259 writeToSet.insert(niceName(varDecl));
261 definitionSet.insert(niceName(varDecl));
262 return true;
265 bool UnusedVarsGlobal::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
267 const Decl* decl = declRefExpr->getDecl();
268 const VarDecl* varDecl = dyn_cast<VarDecl>(decl);
269 if (!varDecl)
270 return true;
271 if (isa<ParmVarDecl>(varDecl))
272 return true;
273 if (!varDecl->hasGlobalStorage())
274 return true;
275 varDecl = varDecl->getCanonicalDecl();
276 if (!varDecl->getLocation().isValid() || ignoreLocation(varDecl))
277 return true;
278 // ignore stuff that forms part of the stable URE interface
279 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
280 return true;
281 checkIfReadFrom(varDecl, declRefExpr);
282 checkIfWrittenTo(varDecl, declRefExpr);
283 return true;
287 Does the expression being used to initialise a field value evaluate to
288 the same as a default value?
290 bool UnusedVarsGlobal::isSomeKindOfZero(const Expr* arg)
292 assert(arg);
293 if (arg->isValueDependent())
294 return false;
295 if (arg->getType().isNull())
296 return false;
297 if (isa<CXXDefaultArgExpr>(arg))
298 arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
299 arg = arg->IgnoreParenCasts();
300 // ignore this, it seems to trigger an infinite recursion
301 if (isa<UnaryExprOrTypeTraitExpr>(arg))
302 return false;
303 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg))
304 return cxxConstructExpr->getConstructor()->isDefaultConstructor();
305 APSInt x1;
306 if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
307 return x1 == 0;
308 if (isa<CXXNullPtrLiteralExpr>(arg))
309 return true;
310 if (isa<MaterializeTemporaryExpr>(arg))
312 const CXXBindTemporaryExpr* strippedArg
313 = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
314 if (strippedArg)
316 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
317 if (temp->getNumArgs() == 0)
319 if (loplugin::TypeCheck(temp->getType())
320 .Class("OUString")
321 .Namespace("rtl")
322 .GlobalNamespace())
323 return true;
324 if (loplugin::TypeCheck(temp->getType())
325 .Class("OString")
326 .Namespace("rtl")
327 .GlobalNamespace())
328 return true;
329 return false;
334 // Get the expression contents.
335 // This helps us find params which are always initialised with something like "OUString()".
336 SourceManager& SM = compiler.getSourceManager();
337 SourceLocation startLoc = arg->getBeginLoc();
338 SourceLocation endLoc = arg->getEndLoc();
339 const char* p1 = SM.getCharacterData(startLoc);
340 const char* p2 = SM.getCharacterData(endLoc);
341 if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40)
342 return false;
343 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
344 std::string s(p1, p2 - p1 + n);
345 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
346 std::replace(s.begin(), s.end(), '\r', ' ');
347 std::replace(s.begin(), s.end(), '\n', ' ');
348 std::replace(s.begin(), s.end(), '\t', ' ');
350 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
351 if (s == "OUString()")
352 return true;
353 else if (s == "OString()")
354 return true;
355 return false;
358 static char easytolower(char in)
360 if (in <= 'Z' && in >= 'A')
361 return in - ('Z' - 'z');
362 return in;
365 bool startswith(const std::string& rStr, const char* pSubStr)
367 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
370 bool UnusedVarsGlobal::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
372 auto copy2 = insideFunctionDecl;
373 insideFunctionDecl = cxxMethodDecl;
374 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
375 insideFunctionDecl = copy2;
376 return ret;
379 bool UnusedVarsGlobal::TraverseFunctionDecl(FunctionDecl* functionDecl)
381 auto copy2 = insideFunctionDecl;
382 insideFunctionDecl = functionDecl;
383 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
384 insideFunctionDecl = copy2;
385 return ret;
388 bool UnusedVarsGlobal::TraverseIfStmt(IfStmt* ifStmt)
390 VarDecl const* varDecl = nullptr;
391 Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
393 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(cond))
395 if (auto cxxConvert = dyn_cast_or_null<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
397 if (cxxConvert->getConversionType()->isBooleanType())
398 if (auto declRefExpr = dyn_cast<DeclRefExpr>(
399 memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
400 if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
401 insideConditionalCheckOfVarSet.push_back(varDecl);
404 else if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond))
406 if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
407 insideConditionalCheckOfVarSet.push_back(varDecl);
410 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
411 if (varDecl)
412 insideConditionalCheckOfVarSet.pop_back();
413 return ret;
416 void UnusedVarsGlobal::checkIfReadFrom(const VarDecl* varDecl, const DeclRefExpr* declRefExpr)
418 auto parentsRange = compiler.getASTContext().getParents(*declRefExpr);
419 const Stmt* child = declRefExpr;
420 const Stmt* parent
421 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
422 // walk up the tree until we find something interesting
423 bool bPotentiallyReadFrom = false;
424 bool bDump = false;
425 auto walkUp = [&]() {
426 child = parent;
427 auto parentsRange = compiler.getASTContext().getParents(*parent);
428 parent = parentsRange.begin() == parentsRange.end() ? nullptr
429 : parentsRange.begin()->get<Stmt>();
433 if (!parent)
435 // check if we're inside a CXXCtorInitializer or a VarDecl
436 auto parentsRange = compiler.getASTContext().getParents(*child);
437 if (parentsRange.begin() != parentsRange.end())
439 const Decl* decl = parentsRange.begin()->get<Decl>();
440 if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
441 bPotentiallyReadFrom = true;
443 if (!bPotentiallyReadFrom)
444 return;
445 break;
447 if (isa<CXXReinterpretCastExpr>(parent))
449 // once we see one of these, there is not much useful we can know
450 bPotentiallyReadFrom = true;
451 break;
453 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
454 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
455 || isa<ExprWithCleanups>(parent))
457 walkUp();
459 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
461 UnaryOperator::Opcode op = unaryOperator->getOpcode();
462 if (declRefExpr->getType()->isArrayType() && op == UO_Deref)
464 // ignore, deref'ing an array does not count as a read
466 else if (op == UO_AddrOf || op == UO_Deref || op == UO_Plus || op == UO_Minus
467 || op == UO_Not || op == UO_LNot)
469 bPotentiallyReadFrom = true;
470 break;
472 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
473 ignore them to find interesting fields that only modified, not usefully read:
474 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
475 But we still walk up in case the result of the expression is used in a read sense.
477 walkUp();
479 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
481 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
482 break;
484 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
486 bPotentiallyReadFrom = ifStmt->getCond() == child;
487 break;
489 else if (auto doStmt = dyn_cast<DoStmt>(parent))
491 bPotentiallyReadFrom = doStmt->getCond() == child;
492 break;
494 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
496 if (arraySubscriptExpr->getIdx() == child)
498 bPotentiallyReadFrom = true;
499 break;
501 walkUp();
503 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
505 BinaryOperator::Opcode op = binaryOp->getOpcode();
506 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
507 || op == BO_RemAssign || op == BO_AddAssign
508 || op == BO_SubAssign || op == BO_ShlAssign
509 || op == BO_ShrAssign || op == BO_AndAssign
510 || op == BO_XorAssign || op == BO_OrAssign;
511 if (binaryOp->getLHS() == child && assignmentOp)
512 break;
513 else
515 bPotentiallyReadFrom = true;
516 break;
519 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
521 auto op = operatorCallExpr->getOperator();
522 const bool assignmentOp = op == OO_Equal || op == OO_StarEqual || op == OO_SlashEqual
523 || op == OO_PercentEqual || op == OO_PlusEqual
524 || op == OO_MinusEqual || op == OO_LessLessEqual
525 || op == OO_AmpEqual || op == OO_CaretEqual
526 || op == OO_PipeEqual;
527 if (operatorCallExpr->getArg(0) == child && assignmentOp)
528 break;
529 else if (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child)
530 break; // this is a write-only call
531 else
533 bPotentiallyReadFrom = true;
534 break;
537 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
539 bool bWriteOnlyCall = false;
540 const CXXMethodDecl* callee = cxxMemberCallExpr->getMethodDecl();
541 if (callee)
543 const Expr* tmp = dyn_cast<Expr>(child);
544 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
546 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
548 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
550 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
551 // which we could scatter around.
552 std::string name = callee->getNameAsString();
553 std::transform(name.begin(), name.end(), name.begin(), easytolower);
554 if (startswith(name, "emplace") || name == "insert" || name == "erase"
555 || name == "remove" || name == "remove_if" || name == "sort"
556 || name == "push_back" || name == "pop_back" || name == "push_front"
557 || name == "pop_front" || name == "reserve" || name == "resize"
558 || name == "reset" || name == "clear" || name == "fill")
559 // write-only modifications to collections
560 bWriteOnlyCall = true;
561 else if (name == "dispose" || name == "disposeAndClear" || name == "swap")
562 // we're abusing the write-only analysis here to look for fields which don't have anything useful
563 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
564 // and VclPtr::disposeAndClear
565 bWriteOnlyCall = true;
568 if (!bWriteOnlyCall)
569 bPotentiallyReadFrom = true;
570 break;
572 else if (auto callExpr = dyn_cast<CallExpr>(parent))
574 bool bWriteOnlyCall = false;
575 // check for calls to ReadXXX(foo) type methods, where foo is write-only
576 auto callee = getCallee(callExpr);
577 if (callee)
579 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
580 // which we could scatter around.
581 std::string name = callee->getNameAsString();
582 std::transform(name.begin(), name.end(), name.begin(), easytolower);
583 if (startswith(name, "read"))
584 // this is a write-only call
585 bWriteOnlyCall = true;
587 if (!bWriteOnlyCall)
588 bPotentiallyReadFrom = true;
589 break;
591 else if (isa<ReturnStmt>(parent) || isa<CXXConstructExpr>(parent)
592 || isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
593 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
594 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
595 || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
596 || isa<MaterializeTemporaryExpr>(parent))
598 bPotentiallyReadFrom = true;
599 break;
601 else if (isa<CXXDeleteExpr>(parent) || isa<UnaryExprOrTypeTraitExpr>(parent)
602 || isa<CXXUnresolvedConstructExpr>(parent) || isa<CompoundStmt>(parent)
603 || isa<LabelStmt>(parent) || isa<CXXForRangeStmt>(parent)
604 || isa<CXXTypeidExpr>(parent) || isa<DefaultStmt>(parent)
605 || isa<GCCAsmStmt>(parent) || isa<LambdaExpr>(parent) // TODO
606 || isa<CXXDefaultArgExpr>(parent) || isa<AtomicExpr>(parent)
607 || isa<VAArgExpr>(parent) || isa<DeclRefExpr>(parent) || isa<ConstantExpr>(parent)
608 || isa<SubstNonTypeTemplateParmExpr>(parent))
610 break;
612 else
614 bPotentiallyReadFrom = true;
615 bDump = true;
616 break;
618 } while (true);
620 if (bDump)
622 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
623 declRefExpr->getBeginLoc())
624 << declRefExpr->getSourceRange();
625 report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
626 << parent->getSourceRange();
627 parent->dump();
628 declRefExpr->dump();
631 if (bPotentiallyReadFrom)
632 readFromSet.insert(niceName(varDecl));
635 void UnusedVarsGlobal::checkIfWrittenTo(const VarDecl* varDecl, const DeclRefExpr* declRefExpr)
637 // if we're inside a block that looks like
638 // if (varDecl)
639 // ...
640 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
641 if (std::find(insideConditionalCheckOfVarSet.begin(), insideConditionalCheckOfVarSet.end(),
642 varDecl)
643 != insideConditionalCheckOfVarSet.end())
644 return;
646 auto parentsRange = compiler.getASTContext().getParents(*declRefExpr);
647 const Stmt* child = declRefExpr;
648 const Stmt* parent
649 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
650 // walk up the tree until we find something interesting
651 bool bPotentiallyWrittenTo = false;
652 bool bDump = false;
653 auto walkUp = [&]() {
654 child = parent;
655 auto parentsRange = compiler.getASTContext().getParents(*parent);
656 parent = parentsRange.begin() == parentsRange.end() ? nullptr
657 : parentsRange.begin()->get<Stmt>();
661 if (!parent)
663 // check if we have an expression like
664 // int& r = m_field;
665 auto parentsRange = compiler.getASTContext().getParents(*child);
666 if (parentsRange.begin() != parentsRange.end())
668 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
669 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
670 // which is of type 'T&&' and also an l-value-ref ?
671 if (varDecl && !varDecl->isImplicit()
672 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
674 bPotentiallyWrittenTo = true;
677 break;
679 if (isa<CXXReinterpretCastExpr>(parent))
681 // once we see one of these, there is not much useful we can know
682 bPotentiallyWrittenTo = true;
683 break;
685 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
686 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
687 || isa<ExprWithCleanups>(parent))
689 walkUp();
691 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
693 UnaryOperator::Opcode op = unaryOperator->getOpcode();
694 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
695 || op == UO_PreDec)
697 bPotentiallyWrittenTo = true;
699 break;
701 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
703 if (arraySubscriptExpr->getIdx() == child)
704 break;
705 walkUp();
707 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
709 auto callee = getCallee(operatorCallExpr);
710 if (callee)
712 // if calling a non-const operator on the field
713 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
714 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
716 if (!calleeMethodDecl->isConst())
717 bPotentiallyWrittenTo
718 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
720 else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
722 bPotentiallyWrittenTo = true;
725 else
726 bPotentiallyWrittenTo = true; // conservative, could improve
727 break;
729 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
731 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
732 if (calleeMethodDecl)
734 // if calling a non-const method on the field
735 const Expr* tmp = dyn_cast<Expr>(child);
736 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
738 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
740 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
742 if (!calleeMethodDecl->isConst())
743 bPotentiallyWrittenTo
744 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
745 break;
747 else if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
748 CalleeWrapper(calleeMethodDecl)))
749 bPotentiallyWrittenTo = true;
751 else
752 bPotentiallyWrittenTo = true; // can happen in templates
753 break;
755 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
757 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
758 CalleeWrapper(cxxConstructExpr)))
759 bPotentiallyWrittenTo = true;
760 break;
762 else if (auto callExpr = dyn_cast<CallExpr>(parent))
764 auto callee = getCallee(callExpr);
765 if (callee)
767 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
768 bPotentiallyWrittenTo = true;
770 else
771 bPotentiallyWrittenTo = true; // conservative, could improve
772 break;
774 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
776 BinaryOperator::Opcode op = binaryOp->getOpcode();
777 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
778 || op == BO_RemAssign || op == BO_AddAssign
779 || op == BO_SubAssign || op == BO_ShlAssign
780 || op == BO_ShrAssign || op == BO_AndAssign
781 || op == BO_XorAssign || op == BO_OrAssign;
782 if (assignmentOp)
784 if (binaryOp->getLHS() == child)
785 bPotentiallyWrittenTo = true;
786 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
787 .LvalueReference()
788 .NonConst())
789 // if the LHS is a non-const reference, we could write to the field later on
790 bPotentiallyWrittenTo = true;
792 break;
794 else if (isa<ReturnStmt>(parent))
796 if (insideFunctionDecl)
798 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
799 if (tc.LvalueReference().NonConst())
800 bPotentiallyWrittenTo = true;
802 break;
804 else if (isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
805 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
806 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
807 || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
808 || isa<MaterializeTemporaryExpr>(parent) || isa<IfStmt>(parent)
809 || isa<DoStmt>(parent) || isa<CXXDeleteExpr>(parent)
810 || isa<UnaryExprOrTypeTraitExpr>(parent) || isa<CXXUnresolvedConstructExpr>(parent)
811 || isa<CompoundStmt>(parent) || isa<LabelStmt>(parent)
812 || isa<CXXForRangeStmt>(parent) || isa<CXXTypeidExpr>(parent)
813 || isa<DefaultStmt>(parent) || isa<GCCAsmStmt>(parent) || isa<ConstantExpr>(parent)
814 || isa<AtomicExpr>(parent) || isa<CXXDefaultArgExpr>(parent)
815 || isa<VAArgExpr>(parent) || isa<DeclRefExpr>(parent)
816 || isa<SubstNonTypeTemplateParmExpr>(parent) || isa<LambdaExpr>(parent)) // TODO
818 break;
820 else
822 bPotentiallyWrittenTo = true;
823 bDump = true;
824 break;
826 } while (true);
828 if (bDump)
830 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
831 declRefExpr->getBeginLoc())
832 << bPotentiallyWrittenTo << declRefExpr->getSourceRange();
833 if (parent)
835 report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
836 << parent->getSourceRange();
837 parent->dump();
839 declRefExpr->dump();
840 varDecl->getType()->dump();
843 if (bPotentiallyWrittenTo)
844 writeToSet.insert(niceName(varDecl));
847 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
848 bool UnusedVarsGlobal::checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl)
850 auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
851 bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
852 if (tc.Class("deque").StdNamespace() || tc.Class("list").StdNamespace()
853 || tc.Class("queue").StdNamespace() || tc.Class("vector").StdNamespace())
855 listLike = true;
857 else if (tc.Class("set").StdNamespace() || tc.Class("unordered_set").StdNamespace())
859 setLike = true;
861 else if (tc.Class("map").StdNamespace() || tc.Class("unordered_map").StdNamespace())
863 mapLike = true;
865 else if (tc.Class("Sequence")
866 .Namespace("uno")
867 .Namespace("star")
868 .Namespace("sun")
869 .Namespace("com")
870 .GlobalNamespace())
872 cssSequence = true;
874 else
875 return true;
877 if (calleeMethodDecl->isOverloadedOperator())
879 auto oo = calleeMethodDecl->getOverloadedOperator();
880 if (oo == OO_Equal)
881 return true;
882 // This is operator[]. We only care about things that add elements to the collection.
883 // if nothing modifies the size of the collection, then nothing useful
884 // is stored in it.
885 if (listLike)
886 return false;
887 return true;
890 auto name = calleeMethodDecl->getName();
891 if (listLike || setLike || mapLike)
893 if (name == "reserve" || name == "shrink_to_fit" || name == "clear" || name == "erase"
894 || name == "pop_back" || name == "pop_front" || name == "front" || name == "back"
895 || name == "data" || name == "remove" || name == "remove_if" || name == "unique"
896 || name == "sort" || name == "begin" || name == "end" || name == "rbegin"
897 || name == "rend" || name == "at" || name == "find" || name == "equal_range"
898 || name == "lower_bound" || name == "upper_bound")
899 return false;
901 if (cssSequence)
903 if (name == "getArray" || name == "begin" || name == "end")
904 return false;
907 return true;
910 bool UnusedVarsGlobal::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child,
911 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
913 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
914 // if it's an array, passing it by value to a method typically means the
915 // callee takes a pointer and can modify the array
916 if (varDecl->getType()->isConstantArrayType())
918 for (unsigned i = 0; i < len; ++i)
919 if (callExpr.getArg(i) == child)
920 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
921 return true;
923 else
925 for (unsigned i = 0; i < len; ++i)
926 if (callExpr.getArg(i) == child)
927 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
928 .LvalueReference()
929 .NonConst())
930 return true;
932 return false;
935 compat::optional<CalleeWrapper> UnusedVarsGlobal::getCallee(CallExpr const* callExpr)
937 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
938 if (functionDecl)
939 return CalleeWrapper(functionDecl);
941 // Extract the functionprototype from a type
942 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
943 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
945 if (auto prototype = pointerType->getPointeeType()
946 ->getUnqualifiedDesugaredType()
947 ->getAs<FunctionProtoType>())
949 return CalleeWrapper(prototype);
953 return compat::optional<CalleeWrapper>();
956 loplugin::Plugin::Registration<UnusedVarsGlobal> X("unusedvarsglobal", false);
959 #endif
961 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */