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