1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
11 
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <unordered_set>
17 #include <vector>
18 #include <algorithm>
19 #include <sys/file.h>
20 #include <unistd.h>
21 
22 #include "config_clang.h"
23 
24 #include "plugin.hxx"
25 #include "compat.hxx"
26 #include "check.hxx"
27 
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
30 #endif
31 
32 /**
33 This performs two analyses:
34  (1) look for unused global vars
35  (2) look for global vars that are write-only
36 */
37 
38 namespace
39 {
40 struct MyVarInfo
41 {
42     const VarDecl* varDecl;
43     std::string fieldName;
44     std::string fieldType;
45     std::string sourceLocation;
46 };
operator <(const MyVarInfo & lhs,const MyVarInfo & rhs)47 bool operator<(const MyVarInfo& lhs, const MyVarInfo& rhs)
48 {
49     return std::tie(lhs.sourceLocation, lhs.fieldName)
50            < std::tie(rhs.sourceLocation, rhs.fieldName);
51 }
52 
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;
57 
58 /**
59  * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
60  */
61 class CallerWrapper
62 {
63     const CallExpr* m_callExpr;
64     const CXXConstructExpr* m_cxxConstructExpr;
65 
66 public:
CallerWrapper(const CallExpr * callExpr)67     CallerWrapper(const CallExpr* callExpr)
68         : m_callExpr(callExpr)
69         , m_cxxConstructExpr(nullptr)
70     {
71     }
CallerWrapper(const CXXConstructExpr * cxxConstructExpr)72     CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
73         : m_callExpr(nullptr)
74         , m_cxxConstructExpr(cxxConstructExpr)
75     {
76     }
getNumArgs() const77     unsigned getNumArgs() const
78     {
79         return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
80     }
getArg(unsigned i) const81     const Expr* getArg(unsigned i) const
82     {
83         return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
84     }
85 };
86 class CalleeWrapper
87 {
88     const FunctionDecl* m_calleeFunctionDecl = nullptr;
89     const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
90     const FunctionProtoType* m_functionPrototype = nullptr;
91 
92 public:
CalleeWrapper(const FunctionDecl * calleeFunctionDecl)93     explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
94         : m_calleeFunctionDecl(calleeFunctionDecl)
95     {
96     }
CalleeWrapper(const CXXConstructExpr * cxxConstructExpr)97     explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
98         : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
99     {
100     }
CalleeWrapper(const FunctionProtoType * functionPrototype)101     explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
102         : m_functionPrototype(functionPrototype)
103     {
104     }
getNumParams() const105     unsigned getNumParams() const
106     {
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();
116     }
getParamType(unsigned i) const117     const QualType getParamType(unsigned i) const
118     {
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];
125     }
getNameAsString() const126     std::string getNameAsString() const
127     {
128         if (m_calleeFunctionDecl)
129             return m_calleeFunctionDecl->getNameAsString();
130         else if (m_cxxConstructorDecl)
131             return m_cxxConstructorDecl->getNameAsString();
132         else
133             return "";
134     }
getAsCXXMethodDecl() const135     CXXMethodDecl const* getAsCXXMethodDecl() const
136     {
137         if (m_calleeFunctionDecl)
138             return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
139         return nullptr;
140     }
141 };
142 
143 class UnusedVarsGlobal : public loplugin::FilteringPlugin<UnusedVarsGlobal>
144 {
145 public:
UnusedVarsGlobal(loplugin::InstantiationData const & data)146     explicit UnusedVarsGlobal(loplugin::InstantiationData const& data)
147         : FilteringPlugin(data)
148     {
149     }
150 
151     virtual void run() override;
152 
shouldVisitTemplateInstantiations() const153     bool shouldVisitTemplateInstantiations() const { return true; }
shouldVisitImplicitCode() const154     bool shouldVisitImplicitCode() const { return true; }
155 
156     bool VisitVarDecl(const VarDecl*);
157     bool VisitDeclRefExpr(const DeclRefExpr*);
158     bool TraverseCXXMethodDecl(CXXMethodDecl*);
159     bool TraverseFunctionDecl(FunctionDecl*);
160     bool TraverseIfStmt(IfStmt*);
161 
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*);
171 
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;
176 };
177 
run()178 void UnusedVarsGlobal::run()
179 {
180     TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
181 
182     if (!isUnitTestMode())
183     {
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();
198     }
199     else
200     {
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));
205     }
206 }
207 
niceName(const VarDecl * varDecl)208 MyVarInfo UnusedVarsGlobal::niceName(const VarDecl* varDecl)
209 {
210     MyVarInfo aInfo;
211     aInfo.varDecl = varDecl;
212 
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)
217     {
218         aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
219     }
220     aInfo.fieldType = varDecl->getType().getAsString();
221 
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);
229 
230     return aInfo;
231 }
232 
VisitVarDecl(const VarDecl * varDecl)233 bool UnusedVarsGlobal::VisitVarDecl(const VarDecl* varDecl)
234 {
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;
245 
246     /**
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.
252     */
253     auto varType = varDecl->getType();
254     if (varType.isConstQualified() && varType->isIntegerType())
255         return true;
256 
257     auto initExpr = varDecl->getAnyInitializer();
258     if (initExpr && !isSomeKindOfZero(initExpr))
259         writeToSet.insert(niceName(varDecl));
260 
261     definitionSet.insert(niceName(varDecl));
262     return true;
263 }
264 
VisitDeclRefExpr(const DeclRefExpr * declRefExpr)265 bool UnusedVarsGlobal::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
266 {
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;
284 }
285 
286 /**
287  Does the expression being used to initialise a field value evaluate to
288  the same as a default value?
289  */
isSomeKindOfZero(const Expr * arg)290 bool UnusedVarsGlobal::isSomeKindOfZero(const Expr* arg)
291 {
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))
311     {
312         const CXXBindTemporaryExpr* strippedArg
313             = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
314         if (strippedArg)
315         {
316             auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
317             if (temp->getNumArgs() == 0)
318             {
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;
330             }
331         }
332     }
333 
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', ' ');
349 
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     else if (s == "aEmptyOUStr") //sw
356         return true;
357     else if (s == "EMPTY_OUSTRING") //sc
358         return true;
359     else if (s == "GetEmptyOUString()") //sc
360         return true;
361     return false;
362 }
363 
easytolower(char in)364 static char easytolower(char in)
365 {
366     if (in <= 'Z' && in >= 'A')
367         return in - ('Z' - 'z');
368     return in;
369 }
370 
startswith(const std::string & rStr,const char * pSubStr)371 bool startswith(const std::string& rStr, const char* pSubStr)
372 {
373     return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
374 }
375 
TraverseCXXMethodDecl(CXXMethodDecl * cxxMethodDecl)376 bool UnusedVarsGlobal::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
377 {
378     auto copy2 = insideFunctionDecl;
379     insideFunctionDecl = cxxMethodDecl;
380     bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
381     insideFunctionDecl = copy2;
382     return ret;
383 }
384 
TraverseFunctionDecl(FunctionDecl * functionDecl)385 bool UnusedVarsGlobal::TraverseFunctionDecl(FunctionDecl* functionDecl)
386 {
387     auto copy2 = insideFunctionDecl;
388     insideFunctionDecl = functionDecl;
389     bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
390     insideFunctionDecl = copy2;
391     return ret;
392 }
393 
TraverseIfStmt(IfStmt * ifStmt)394 bool UnusedVarsGlobal::TraverseIfStmt(IfStmt* ifStmt)
395 {
396     VarDecl const* varDecl = nullptr;
397     Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
398 
399     if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(cond))
400     {
401         if (auto cxxConvert = dyn_cast_or_null<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
402         {
403             if (cxxConvert->getConversionType()->isBooleanType())
404                 if (auto declRefExpr = dyn_cast<DeclRefExpr>(
405                         memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
406                     if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
407                         insideConditionalCheckOfVarSet.push_back(varDecl);
408         }
409     }
410     else if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond))
411     {
412         if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
413             insideConditionalCheckOfVarSet.push_back(varDecl);
414     }
415 
416     bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
417     if (varDecl)
418         insideConditionalCheckOfVarSet.pop_back();
419     return ret;
420 }
421 
checkIfReadFrom(const VarDecl * varDecl,const DeclRefExpr * declRefExpr)422 void UnusedVarsGlobal::checkIfReadFrom(const VarDecl* varDecl, const DeclRefExpr* declRefExpr)
423 {
424     auto parentsRange = compiler.getASTContext().getParents(*declRefExpr);
425     const Stmt* child = declRefExpr;
426     const Stmt* parent
427         = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
428     // walk up the tree until we find something interesting
429     bool bPotentiallyReadFrom = false;
430     bool bDump = false;
431     auto walkUp = [&]() {
432         child = parent;
433         auto parentsRange = compiler.getASTContext().getParents(*parent);
434         parent = parentsRange.begin() == parentsRange.end() ? nullptr
435                                                             : parentsRange.begin()->get<Stmt>();
436     };
437     do
438     {
439         if (!parent)
440         {
441             // check if we're inside a CXXCtorInitializer or a VarDecl
442             auto parentsRange = compiler.getASTContext().getParents(*child);
443             if (parentsRange.begin() != parentsRange.end())
444             {
445                 const Decl* decl = parentsRange.begin()->get<Decl>();
446                 if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
447                     bPotentiallyReadFrom = true;
448             }
449             if (!bPotentiallyReadFrom)
450                 return;
451             break;
452         }
453         if (isa<CXXReinterpretCastExpr>(parent))
454         {
455             // once we see one of these, there is not much useful we can know
456             bPotentiallyReadFrom = true;
457             break;
458         }
459         else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
460                  || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
461                  || isa<ExprWithCleanups>(parent))
462         {
463             walkUp();
464         }
465         else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
466         {
467             UnaryOperator::Opcode op = unaryOperator->getOpcode();
468             if (declRefExpr->getType()->isArrayType() && op == UO_Deref)
469             {
470                 // ignore, deref'ing an array does not count as a read
471             }
472             else if (op == UO_AddrOf || op == UO_Deref || op == UO_Plus || op == UO_Minus
473                      || op == UO_Not || op == UO_LNot)
474             {
475                 bPotentiallyReadFrom = true;
476                 break;
477             }
478             /* The following are technically reads, but from a code-sense they're more of a write/modify, so
479                 ignore them to find interesting fields that only modified, not usefully read:
480                 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
481                 But we still walk up in case the result of the expression is used in a read sense.
482             */
483             walkUp();
484         }
485         else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
486         {
487             bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
488             break;
489         }
490         else if (auto ifStmt = dyn_cast<IfStmt>(parent))
491         {
492             bPotentiallyReadFrom = ifStmt->getCond() == child;
493             break;
494         }
495         else if (auto doStmt = dyn_cast<DoStmt>(parent))
496         {
497             bPotentiallyReadFrom = doStmt->getCond() == child;
498             break;
499         }
500         else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
501         {
502             if (arraySubscriptExpr->getIdx() == child)
503             {
504                 bPotentiallyReadFrom = true;
505                 break;
506             }
507             walkUp();
508         }
509         else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
510         {
511             BinaryOperator::Opcode op = binaryOp->getOpcode();
512             const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
513                                       || op == BO_RemAssign || op == BO_AddAssign
514                                       || op == BO_SubAssign || op == BO_ShlAssign
515                                       || op == BO_ShrAssign || op == BO_AndAssign
516                                       || op == BO_XorAssign || op == BO_OrAssign;
517             if (binaryOp->getLHS() == child && assignmentOp)
518                 break;
519             else
520             {
521                 bPotentiallyReadFrom = true;
522                 break;
523             }
524         }
525         else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
526         {
527             auto op = operatorCallExpr->getOperator();
528             const bool assignmentOp = op == OO_Equal || op == OO_StarEqual || op == OO_SlashEqual
529                                       || op == OO_PercentEqual || op == OO_PlusEqual
530                                       || op == OO_MinusEqual || op == OO_LessLessEqual
531                                       || op == OO_AmpEqual || op == OO_CaretEqual
532                                       || op == OO_PipeEqual;
533             if (operatorCallExpr->getArg(0) == child && assignmentOp)
534                 break;
535             else if (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child)
536                 break; // this is a write-only call
537             else
538             {
539                 bPotentiallyReadFrom = true;
540                 break;
541             }
542         }
543         else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
544         {
545             bool bWriteOnlyCall = false;
546             const CXXMethodDecl* callee = cxxMemberCallExpr->getMethodDecl();
547             if (callee)
548             {
549                 const Expr* tmp = dyn_cast<Expr>(child);
550                 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
551                 {
552                     tmp = dyn_cast<MemberExpr>(tmp)->getBase();
553                 }
554                 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
555                 {
556                     // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
557                     // which we could scatter around.
558                     std::string name = callee->getNameAsString();
559                     std::transform(name.begin(), name.end(), name.begin(), easytolower);
560                     if (startswith(name, "emplace") || name == "insert" || name == "erase"
561                         || name == "remove" || name == "remove_if" || name == "sort"
562                         || name == "push_back" || name == "pop_back" || name == "push_front"
563                         || name == "pop_front" || name == "reserve" || name == "resize"
564                         || name == "reset" || name == "clear" || name == "fill")
565                         // write-only modifications to collections
566                         bWriteOnlyCall = true;
567                     else if (name == "dispose" || name == "disposeAndClear" || name == "swap")
568                         // we're abusing the write-only analysis here to look for fields which don't have anything useful
569                         // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
570                         // and VclPtr::disposeAndClear
571                         bWriteOnlyCall = true;
572                 }
573             }
574             if (!bWriteOnlyCall)
575                 bPotentiallyReadFrom = true;
576             break;
577         }
578         else if (auto callExpr = dyn_cast<CallExpr>(parent))
579         {
580             bool bWriteOnlyCall = false;
581             // check for calls to ReadXXX(foo) type methods, where foo is write-only
582             auto callee = getCallee(callExpr);
583             if (callee)
584             {
585                 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
586                 // which we could scatter around.
587                 std::string name = callee->getNameAsString();
588                 std::transform(name.begin(), name.end(), name.begin(), easytolower);
589                 if (startswith(name, "read"))
590                     // this is a write-only call
591                     bWriteOnlyCall = true;
592             }
593             if (!bWriteOnlyCall)
594                 bPotentiallyReadFrom = true;
595             break;
596         }
597         else if (isa<ReturnStmt>(parent) || isa<CXXConstructExpr>(parent)
598                  || isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
599                  || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
600                  || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
601                  || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
602                  || isa<MaterializeTemporaryExpr>(parent))
603         {
604             bPotentiallyReadFrom = true;
605             break;
606         }
607         else if (isa<CXXDeleteExpr>(parent) || isa<UnaryExprOrTypeTraitExpr>(parent)
608                  || isa<CXXUnresolvedConstructExpr>(parent) || isa<CompoundStmt>(parent)
609                  || isa<LabelStmt>(parent) || isa<CXXForRangeStmt>(parent)
610                  || isa<CXXTypeidExpr>(parent) || isa<DefaultStmt>(parent)
611                  || isa<GCCAsmStmt>(parent) || isa<LambdaExpr>(parent) // TODO
612                  || isa<CXXDefaultArgExpr>(parent) || isa<AtomicExpr>(parent)
613                  || isa<VAArgExpr>(parent) || isa<DeclRefExpr>(parent)
614 #if CLANG_VERSION >= 80000
615                  || isa<ConstantExpr>(parent)
616 #endif
617                  || isa<SubstNonTypeTemplateParmExpr>(parent))
618         {
619             break;
620         }
621         else
622         {
623             bPotentiallyReadFrom = true;
624             bDump = true;
625             break;
626         }
627     } while (true);
628 
629     if (bDump)
630     {
631         report(DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
632                compat::getBeginLoc(declRefExpr))
633             << declRefExpr->getSourceRange();
634         report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
635             << parent->getSourceRange();
636         parent->dump();
637         declRefExpr->dump();
638     }
639 
640     if (bPotentiallyReadFrom)
641         readFromSet.insert(niceName(varDecl));
642 }
643 
checkIfWrittenTo(const VarDecl * varDecl,const DeclRefExpr * declRefExpr)644 void UnusedVarsGlobal::checkIfWrittenTo(const VarDecl* varDecl, const DeclRefExpr* declRefExpr)
645 {
646     // if we're inside a block that looks like
647     //   if (varDecl)
648     //       ...
649     // then writes to this field don't matter, because unless we find another write to this field, this field is dead
650     if (std::find(insideConditionalCheckOfVarSet.begin(), insideConditionalCheckOfVarSet.end(),
651                   varDecl)
652         != insideConditionalCheckOfVarSet.end())
653         return;
654 
655     auto parentsRange = compiler.getASTContext().getParents(*declRefExpr);
656     const Stmt* child = declRefExpr;
657     const Stmt* parent
658         = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
659     // walk up the tree until we find something interesting
660     bool bPotentiallyWrittenTo = false;
661     bool bDump = false;
662     auto walkUp = [&]() {
663         child = parent;
664         auto parentsRange = compiler.getASTContext().getParents(*parent);
665         parent = parentsRange.begin() == parentsRange.end() ? nullptr
666                                                             : parentsRange.begin()->get<Stmt>();
667     };
668     do
669     {
670         if (!parent)
671         {
672             // check if we have an expression like
673             //    int& r = m_field;
674             auto parentsRange = compiler.getASTContext().getParents(*child);
675             if (parentsRange.begin() != parentsRange.end())
676             {
677                 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
678                 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
679                 // which is of type 'T&&' and also an l-value-ref ?
680                 if (varDecl && !varDecl->isImplicit()
681                     && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
682                 {
683                     bPotentiallyWrittenTo = true;
684                 }
685             }
686             break;
687         }
688         if (isa<CXXReinterpretCastExpr>(parent))
689         {
690             // once we see one of these, there is not much useful we can know
691             bPotentiallyWrittenTo = true;
692             break;
693         }
694         else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
695                  || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
696                  || isa<ExprWithCleanups>(parent))
697         {
698             walkUp();
699         }
700         else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
701         {
702             UnaryOperator::Opcode op = unaryOperator->getOpcode();
703             if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
704                 || op == UO_PreDec)
705             {
706                 bPotentiallyWrittenTo = true;
707             }
708             break;
709         }
710         else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
711         {
712             if (arraySubscriptExpr->getIdx() == child)
713                 break;
714             walkUp();
715         }
716         else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
717         {
718             auto callee = getCallee(operatorCallExpr);
719             if (callee)
720             {
721                 // if calling a non-const operator on the field
722                 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
723                 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
724                 {
725                     if (!calleeMethodDecl->isConst())
726                         bPotentiallyWrittenTo
727                             = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
728                 }
729                 else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
730                 {
731                     bPotentiallyWrittenTo = true;
732                 }
733             }
734             else
735                 bPotentiallyWrittenTo = true; // conservative, could improve
736             break;
737         }
738         else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
739         {
740             const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
741             if (calleeMethodDecl)
742             {
743                 // if calling a non-const method on the field
744                 const Expr* tmp = dyn_cast<Expr>(child);
745                 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
746                 {
747                     tmp = dyn_cast<MemberExpr>(tmp)->getBase();
748                 }
749                 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
750                 {
751                     if (!calleeMethodDecl->isConst())
752                         bPotentiallyWrittenTo
753                             = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
754                     break;
755                 }
756                 else if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
757                                             CalleeWrapper(calleeMethodDecl)))
758                     bPotentiallyWrittenTo = true;
759             }
760             else
761                 bPotentiallyWrittenTo = true; // can happen in templates
762             break;
763         }
764         else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
765         {
766             if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
767                                    CalleeWrapper(cxxConstructExpr)))
768                 bPotentiallyWrittenTo = true;
769             break;
770         }
771         else if (auto callExpr = dyn_cast<CallExpr>(parent))
772         {
773             auto callee = getCallee(callExpr);
774             if (callee)
775             {
776                 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
777                     bPotentiallyWrittenTo = true;
778             }
779             else
780                 bPotentiallyWrittenTo = true; // conservative, could improve
781             break;
782         }
783         else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
784         {
785             BinaryOperator::Opcode op = binaryOp->getOpcode();
786             const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
787                                       || op == BO_RemAssign || op == BO_AddAssign
788                                       || op == BO_SubAssign || op == BO_ShlAssign
789                                       || op == BO_ShrAssign || op == BO_AndAssign
790                                       || op == BO_XorAssign || op == BO_OrAssign;
791             if (assignmentOp)
792             {
793                 if (binaryOp->getLHS() == child)
794                     bPotentiallyWrittenTo = true;
795                 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
796                              .LvalueReference()
797                              .NonConst())
798                     // if the LHS is a non-const reference, we could write to the field later on
799                     bPotentiallyWrittenTo = true;
800             }
801             break;
802         }
803         else if (isa<ReturnStmt>(parent))
804         {
805             if (insideFunctionDecl)
806             {
807                 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
808                 if (tc.LvalueReference().NonConst())
809                     bPotentiallyWrittenTo = true;
810             }
811             break;
812         }
813         else if (isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
814                  || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
815                  || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
816                  || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
817                  || isa<MaterializeTemporaryExpr>(parent) || isa<IfStmt>(parent)
818                  || isa<DoStmt>(parent) || isa<CXXDeleteExpr>(parent)
819                  || isa<UnaryExprOrTypeTraitExpr>(parent) || isa<CXXUnresolvedConstructExpr>(parent)
820                  || isa<CompoundStmt>(parent) || isa<LabelStmt>(parent)
821                  || isa<CXXForRangeStmt>(parent) || isa<CXXTypeidExpr>(parent)
822                  || isa<DefaultStmt>(parent) || isa<GCCAsmStmt>(parent)
823 #if CLANG_VERSION >= 80000
824                  || isa<ConstantExpr>(parent)
825 #endif
826                  || isa<AtomicExpr>(parent) || isa<CXXDefaultArgExpr>(parent)
827                  || isa<VAArgExpr>(parent) || isa<DeclRefExpr>(parent)
828                  || isa<SubstNonTypeTemplateParmExpr>(parent) || isa<LambdaExpr>(parent)) // TODO
829         {
830             break;
831         }
832         else
833         {
834             bPotentiallyWrittenTo = true;
835             bDump = true;
836             break;
837         }
838     } while (true);
839 
840     if (bDump)
841     {
842         report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
843                compat::getBeginLoc(declRefExpr))
844             << bPotentiallyWrittenTo << declRefExpr->getSourceRange();
845         if (parent)
846         {
847             report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
848                 << parent->getSourceRange();
849             parent->dump();
850         }
851         declRefExpr->dump();
852         varDecl->getType()->dump();
853     }
854 
855     if (bPotentiallyWrittenTo)
856         writeToSet.insert(niceName(varDecl));
857 }
858 
859 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl)860 bool UnusedVarsGlobal::checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl)
861 {
862     auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
863     bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
864     if (tc.Class("deque").StdNamespace() || tc.Class("list").StdNamespace()
865         || tc.Class("queue").StdNamespace() || tc.Class("vector").StdNamespace())
866     {
867         listLike = true;
868     }
869     else if (tc.Class("set").StdNamespace() || tc.Class("unordered_set").StdNamespace())
870     {
871         setLike = true;
872     }
873     else if (tc.Class("map").StdNamespace() || tc.Class("unordered_map").StdNamespace())
874     {
875         mapLike = true;
876     }
877     else if (tc.Class("Sequence")
878                  .Namespace("uno")
879                  .Namespace("star")
880                  .Namespace("sun")
881                  .Namespace("com")
882                  .GlobalNamespace())
883     {
884         cssSequence = true;
885     }
886     else
887         return true;
888 
889     if (calleeMethodDecl->isOverloadedOperator())
890     {
891         auto oo = calleeMethodDecl->getOverloadedOperator();
892         if (oo == OO_Equal)
893             return true;
894         // This is operator[]. We only care about things that add elements to the collection.
895         // if nothing modifies the size of the collection, then nothing useful
896         // is stored in it.
897         if (listLike)
898             return false;
899         return true;
900     }
901 
902     auto name = calleeMethodDecl->getName();
903     if (listLike || setLike || mapLike)
904     {
905         if (name == "reserve" || name == "shrink_to_fit" || name == "clear" || name == "erase"
906             || name == "pop_back" || name == "pop_front" || name == "front" || name == "back"
907             || name == "data" || name == "remove" || name == "remove_if" || name == "unique"
908             || name == "sort" || name == "begin" || name == "end" || name == "rbegin"
909             || name == "rend" || name == "at" || name == "find" || name == "equal_range"
910             || name == "lower_bound" || name == "upper_bound")
911             return false;
912     }
913     if (cssSequence)
914     {
915         if (name == "getArray" || name == "begin" || name == "end")
916             return false;
917     }
918 
919     return true;
920 }
921 
IsPassedByNonConst(const VarDecl * varDecl,const Stmt * child,CallerWrapper callExpr,CalleeWrapper calleeFunctionDecl)922 bool UnusedVarsGlobal::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child,
923                                           CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
924 {
925     unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
926     // if it's an array, passing it by value to a method typically means the
927     // callee takes a pointer and can modify the array
928     if (varDecl->getType()->isConstantArrayType())
929     {
930         for (unsigned i = 0; i < len; ++i)
931             if (callExpr.getArg(i) == child)
932                 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
933                     return true;
934     }
935     else
936     {
937         for (unsigned i = 0; i < len; ++i)
938             if (callExpr.getArg(i) == child)
939                 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
940                         .LvalueReference()
941                         .NonConst())
942                     return true;
943     }
944     return false;
945 }
946 
getCallee(CallExpr const * callExpr)947 llvm::Optional<CalleeWrapper> UnusedVarsGlobal::getCallee(CallExpr const* callExpr)
948 {
949     FunctionDecl const* functionDecl = callExpr->getDirectCallee();
950     if (functionDecl)
951         return CalleeWrapper(functionDecl);
952 
953     // Extract the functionprototype from a type
954     clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
955     if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
956     {
957         if (auto prototype = pointerType->getPointeeType()
958                                  ->getUnqualifiedDesugaredType()
959                                  ->getAs<FunctionProtoType>())
960         {
961             return CalleeWrapper(prototype);
962         }
963     }
964 
965     return llvm::Optional<CalleeWrapper>();
966 }
967 
968 loplugin::Plugin::Registration<UnusedVarsGlobal> X("unusedvarsglobal", false);
969 }
970 
971 #endif
972 
973 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
974