LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / unusedvarsglobal.cxx
blob9362a0117ac6c5ffcf7cc53a87f6f31aec0137e1
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 global vars
35 (2) look for global vars that are write-only
38 namespace
40 struct MyVarInfo
42 const VarDecl* varDecl;
43 std::string fieldName;
44 std::string fieldType;
45 std::string sourceLocation;
47 bool operator<(const MyVarInfo& lhs, const MyVarInfo& rhs)
49 return std::tie(lhs.sourceLocation, lhs.fieldName)
50 < std::tie(rhs.sourceLocation, rhs.fieldName);
53 // try to limit the voluminous output a little
54 static std::set<MyVarInfo> readFromSet;
55 static std::set<MyVarInfo> writeToSet;
56 static std::set<MyVarInfo> definitionSet;
58 /**
59 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
61 class CallerWrapper
63 const CallExpr* m_callExpr;
64 const CXXConstructExpr* m_cxxConstructExpr;
66 public:
67 CallerWrapper(const CallExpr* callExpr)
68 : m_callExpr(callExpr)
69 , m_cxxConstructExpr(nullptr)
72 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
73 : m_callExpr(nullptr)
74 , m_cxxConstructExpr(cxxConstructExpr)
77 unsigned getNumArgs() const
79 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
81 const Expr* getArg(unsigned i) const
83 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
86 class CalleeWrapper
88 const FunctionDecl* m_calleeFunctionDecl = nullptr;
89 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
90 const FunctionProtoType* m_functionPrototype = nullptr;
92 public:
93 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
94 : m_calleeFunctionDecl(calleeFunctionDecl)
97 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
98 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
101 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
102 : m_functionPrototype(functionPrototype)
105 unsigned getNumParams() const
107 if (m_calleeFunctionDecl)
108 return m_calleeFunctionDecl->getNumParams();
109 else if (m_cxxConstructorDecl)
110 return m_cxxConstructorDecl->getNumParams();
111 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
112 // FunctionProtoType will assert if we call getParamTypes() and it has no params
113 return 0;
114 else
115 return m_functionPrototype->getParamTypes().size();
117 const QualType getParamType(unsigned i) const
119 if (m_calleeFunctionDecl)
120 return m_calleeFunctionDecl->getParamDecl(i)->getType();
121 else if (m_cxxConstructorDecl)
122 return m_cxxConstructorDecl->getParamDecl(i)->getType();
123 else
124 return m_functionPrototype->getParamTypes()[i];
126 std::string getNameAsString() const
128 if (m_calleeFunctionDecl)
129 return m_calleeFunctionDecl->getNameAsString();
130 else if (m_cxxConstructorDecl)
131 return m_cxxConstructorDecl->getNameAsString();
132 else
133 return "";
135 CXXMethodDecl const* getAsCXXMethodDecl() const
137 if (m_calleeFunctionDecl)
138 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
139 return nullptr;
143 class UnusedVarsGlobal : public loplugin::FilteringPlugin<UnusedVarsGlobal>
145 public:
146 explicit UnusedVarsGlobal(loplugin::InstantiationData const& data)
147 : FilteringPlugin(data)
151 virtual void run() override;
153 bool shouldVisitTemplateInstantiations() const { return true; }
154 bool shouldVisitImplicitCode() const { return true; }
156 bool VisitVarDecl(const VarDecl*);
157 bool VisitDeclRefExpr(const DeclRefExpr*);
158 bool TraverseCXXMethodDecl(CXXMethodDecl*);
159 bool TraverseFunctionDecl(FunctionDecl*);
160 bool TraverseIfStmt(IfStmt*);
162 private:
163 MyVarInfo niceName(const VarDecl*);
164 void checkIfReadFrom(const VarDecl* fieldDecl, const DeclRefExpr* declRefExpr);
165 void checkIfWrittenTo(const VarDecl* fieldDecl, const DeclRefExpr* declRefExpr);
166 bool isSomeKindOfZero(const Expr* arg);
167 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl);
168 bool IsPassedByNonConst(const VarDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
169 CalleeWrapper calleeFunctionDecl);
170 llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
172 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
173 // we store the parent function on the way down the AST.
174 FunctionDecl* insideFunctionDecl = nullptr;
175 std::vector<VarDecl const*> insideConditionalCheckOfVarSet;
178 void UnusedVarsGlobal::run()
180 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
182 if (!isUnitTestMode())
184 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
185 // writing to the same logfile
186 std::string output;
187 for (const 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", compat::getBeginLoc(s.varDecl));
203 for (const MyVarInfo& s : writeToSet)
204 report(DiagnosticsEngine::Warning, "write", compat::getBeginLoc(s.varDecl));
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 = compat::getBeginLoc(arg);
338 SourceLocation endLoc = compat::getEndLoc(arg);
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)
608 #if CLANG_VERSION >= 80000
609 || isa<ConstantExpr>(parent)
610 #endif
611 || isa<SubstNonTypeTemplateParmExpr>(parent))
613 break;
615 else
617 bPotentiallyReadFrom = true;
618 bDump = true;
619 break;
621 } while (true);
623 if (bDump)
625 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
626 compat::getBeginLoc(declRefExpr))
627 << declRefExpr->getSourceRange();
628 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
629 << parent->getSourceRange();
630 parent->dump();
631 declRefExpr->dump();
634 if (bPotentiallyReadFrom)
635 readFromSet.insert(niceName(varDecl));
638 void UnusedVarsGlobal::checkIfWrittenTo(const VarDecl* varDecl, const DeclRefExpr* declRefExpr)
640 // if we're inside a block that looks like
641 // if (varDecl)
642 // ...
643 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
644 if (std::find(insideConditionalCheckOfVarSet.begin(), insideConditionalCheckOfVarSet.end(),
645 varDecl)
646 != insideConditionalCheckOfVarSet.end())
647 return;
649 auto parentsRange = compiler.getASTContext().getParents(*declRefExpr);
650 const Stmt* child = declRefExpr;
651 const Stmt* parent
652 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
653 // walk up the tree until we find something interesting
654 bool bPotentiallyWrittenTo = false;
655 bool bDump = false;
656 auto walkUp = [&]() {
657 child = parent;
658 auto parentsRange = compiler.getASTContext().getParents(*parent);
659 parent = parentsRange.begin() == parentsRange.end() ? nullptr
660 : parentsRange.begin()->get<Stmt>();
664 if (!parent)
666 // check if we have an expression like
667 // int& r = m_field;
668 auto parentsRange = compiler.getASTContext().getParents(*child);
669 if (parentsRange.begin() != parentsRange.end())
671 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
672 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
673 // which is of type 'T&&' and also an l-value-ref ?
674 if (varDecl && !varDecl->isImplicit()
675 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
677 bPotentiallyWrittenTo = true;
680 break;
682 if (isa<CXXReinterpretCastExpr>(parent))
684 // once we see one of these, there is not much useful we can know
685 bPotentiallyWrittenTo = true;
686 break;
688 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
689 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
690 || isa<ExprWithCleanups>(parent))
692 walkUp();
694 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
696 UnaryOperator::Opcode op = unaryOperator->getOpcode();
697 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
698 || op == UO_PreDec)
700 bPotentiallyWrittenTo = true;
702 break;
704 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
706 if (arraySubscriptExpr->getIdx() == child)
707 break;
708 walkUp();
710 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
712 auto callee = getCallee(operatorCallExpr);
713 if (callee)
715 // if calling a non-const operator on the field
716 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
717 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
719 if (!calleeMethodDecl->isConst())
720 bPotentiallyWrittenTo
721 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
723 else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
725 bPotentiallyWrittenTo = true;
728 else
729 bPotentiallyWrittenTo = true; // conservative, could improve
730 break;
732 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
734 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
735 if (calleeMethodDecl)
737 // if calling a non-const method on the field
738 const Expr* tmp = dyn_cast<Expr>(child);
739 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
741 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
743 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
745 if (!calleeMethodDecl->isConst())
746 bPotentiallyWrittenTo
747 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
748 break;
750 else if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
751 CalleeWrapper(calleeMethodDecl)))
752 bPotentiallyWrittenTo = true;
754 else
755 bPotentiallyWrittenTo = true; // can happen in templates
756 break;
758 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
760 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
761 CalleeWrapper(cxxConstructExpr)))
762 bPotentiallyWrittenTo = true;
763 break;
765 else if (auto callExpr = dyn_cast<CallExpr>(parent))
767 auto callee = getCallee(callExpr);
768 if (callee)
770 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
771 bPotentiallyWrittenTo = true;
773 else
774 bPotentiallyWrittenTo = true; // conservative, could improve
775 break;
777 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
779 BinaryOperator::Opcode op = binaryOp->getOpcode();
780 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
781 || op == BO_RemAssign || op == BO_AddAssign
782 || op == BO_SubAssign || op == BO_ShlAssign
783 || op == BO_ShrAssign || op == BO_AndAssign
784 || op == BO_XorAssign || op == BO_OrAssign;
785 if (assignmentOp)
787 if (binaryOp->getLHS() == child)
788 bPotentiallyWrittenTo = true;
789 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
790 .LvalueReference()
791 .NonConst())
792 // if the LHS is a non-const reference, we could write to the field later on
793 bPotentiallyWrittenTo = true;
795 break;
797 else if (isa<ReturnStmt>(parent))
799 if (insideFunctionDecl)
801 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
802 if (tc.LvalueReference().NonConst())
803 bPotentiallyWrittenTo = true;
805 break;
807 else if (isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
808 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
809 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
810 || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
811 || isa<MaterializeTemporaryExpr>(parent) || isa<IfStmt>(parent)
812 || isa<DoStmt>(parent) || isa<CXXDeleteExpr>(parent)
813 || isa<UnaryExprOrTypeTraitExpr>(parent) || isa<CXXUnresolvedConstructExpr>(parent)
814 || isa<CompoundStmt>(parent) || isa<LabelStmt>(parent)
815 || isa<CXXForRangeStmt>(parent) || isa<CXXTypeidExpr>(parent)
816 || isa<DefaultStmt>(parent) || isa<GCCAsmStmt>(parent)
817 #if CLANG_VERSION >= 80000
818 || isa<ConstantExpr>(parent)
819 #endif
820 || isa<AtomicExpr>(parent) || isa<CXXDefaultArgExpr>(parent)
821 || isa<VAArgExpr>(parent) || isa<DeclRefExpr>(parent)
822 || isa<SubstNonTypeTemplateParmExpr>(parent) || isa<LambdaExpr>(parent)) // TODO
824 break;
826 else
828 bPotentiallyWrittenTo = true;
829 bDump = true;
830 break;
832 } while (true);
834 if (bDump)
836 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
837 compat::getBeginLoc(declRefExpr))
838 << bPotentiallyWrittenTo << declRefExpr->getSourceRange();
839 if (parent)
841 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
842 << parent->getSourceRange();
843 parent->dump();
845 declRefExpr->dump();
846 varDecl->getType()->dump();
849 if (bPotentiallyWrittenTo)
850 writeToSet.insert(niceName(varDecl));
853 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
854 bool UnusedVarsGlobal::checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl)
856 auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
857 bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
858 if (tc.Class("deque").StdNamespace() || tc.Class("list").StdNamespace()
859 || tc.Class("queue").StdNamespace() || tc.Class("vector").StdNamespace())
861 listLike = true;
863 else if (tc.Class("set").StdNamespace() || tc.Class("unordered_set").StdNamespace())
865 setLike = true;
867 else if (tc.Class("map").StdNamespace() || tc.Class("unordered_map").StdNamespace())
869 mapLike = true;
871 else if (tc.Class("Sequence")
872 .Namespace("uno")
873 .Namespace("star")
874 .Namespace("sun")
875 .Namespace("com")
876 .GlobalNamespace())
878 cssSequence = true;
880 else
881 return true;
883 if (calleeMethodDecl->isOverloadedOperator())
885 auto oo = calleeMethodDecl->getOverloadedOperator();
886 if (oo == OO_Equal)
887 return true;
888 // This is operator[]. We only care about things that add elements to the collection.
889 // if nothing modifies the size of the collection, then nothing useful
890 // is stored in it.
891 if (listLike)
892 return false;
893 return true;
896 auto name = calleeMethodDecl->getName();
897 if (listLike || setLike || mapLike)
899 if (name == "reserve" || name == "shrink_to_fit" || name == "clear" || name == "erase"
900 || name == "pop_back" || name == "pop_front" || name == "front" || name == "back"
901 || name == "data" || name == "remove" || name == "remove_if" || name == "unique"
902 || name == "sort" || name == "begin" || name == "end" || name == "rbegin"
903 || name == "rend" || name == "at" || name == "find" || name == "equal_range"
904 || name == "lower_bound" || name == "upper_bound")
905 return false;
907 if (cssSequence)
909 if (name == "getArray" || name == "begin" || name == "end")
910 return false;
913 return true;
916 bool UnusedVarsGlobal::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child,
917 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
919 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
920 // if it's an array, passing it by value to a method typically means the
921 // callee takes a pointer and can modify the array
922 if (varDecl->getType()->isConstantArrayType())
924 for (unsigned i = 0; i < len; ++i)
925 if (callExpr.getArg(i) == child)
926 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
927 return true;
929 else
931 for (unsigned i = 0; i < len; ++i)
932 if (callExpr.getArg(i) == child)
933 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
934 .LvalueReference()
935 .NonConst())
936 return true;
938 return false;
941 llvm::Optional<CalleeWrapper> UnusedVarsGlobal::getCallee(CallExpr const* callExpr)
943 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
944 if (functionDecl)
945 return CalleeWrapper(functionDecl);
947 // Extract the functionprototype from a type
948 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
949 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
951 if (auto prototype = pointerType->getPointeeType()
952 ->getUnqualifiedDesugaredType()
953 ->getAs<FunctionProtoType>())
955 return CalleeWrapper(prototype);
959 return llvm::Optional<CalleeWrapper>();
962 loplugin::Plugin::Registration<UnusedVarsGlobal> X("unusedvarsglobal", false);
965 #endif
967 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */