1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
16 #include <unordered_set>
22 #include "config_clang.h"
28 #include "clang/AST/ParentMapContext.h"
31 This performs two analyses:
32 (1) look for unused global vars
33 (2) look for global vars that are write-only
40 const VarDecl
* varDecl
;
41 std::string fieldName
;
42 std::string fieldType
;
43 std::string sourceLocation
;
45 bool operator<(const MyVarInfo
& lhs
, const MyVarInfo
& rhs
)
47 return std::tie(lhs
.sourceLocation
, lhs
.fieldName
)
48 < std::tie(rhs
.sourceLocation
, rhs
.fieldName
);
51 // try to limit the voluminous output a little
52 static std::set
<MyVarInfo
> readFromSet
;
53 static std::set
<MyVarInfo
> writeToSet
;
54 static std::set
<MyVarInfo
> definitionSet
;
57 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
61 const CallExpr
* m_callExpr
;
62 const CXXConstructExpr
* m_cxxConstructExpr
;
65 CallerWrapper(const CallExpr
* callExpr
)
66 : m_callExpr(callExpr
)
67 , m_cxxConstructExpr(nullptr)
70 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
72 , m_cxxConstructExpr(cxxConstructExpr
)
75 unsigned getNumArgs() const
77 return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs();
79 const Expr
* getArg(unsigned i
) const
81 return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
);
86 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
87 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
88 const FunctionProtoType
* m_functionPrototype
= nullptr;
91 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
)
92 : m_calleeFunctionDecl(calleeFunctionDecl
)
95 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
)
96 : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor())
99 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
)
100 : m_functionPrototype(functionPrototype
)
103 unsigned getNumParams() const
105 if (m_calleeFunctionDecl
)
106 return m_calleeFunctionDecl
->getNumParams();
107 else if (m_cxxConstructorDecl
)
108 return m_cxxConstructorDecl
->getNumParams();
109 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
110 // FunctionProtoType will assert if we call getParamTypes() and it has no params
113 return m_functionPrototype
->getParamTypes().size();
115 const QualType
getParamType(unsigned i
) const
117 if (m_calleeFunctionDecl
)
118 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
119 else if (m_cxxConstructorDecl
)
120 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
122 return m_functionPrototype
->getParamTypes()[i
];
124 std::string
getNameAsString() const
126 if (m_calleeFunctionDecl
)
127 return m_calleeFunctionDecl
->getNameAsString();
128 else if (m_cxxConstructorDecl
)
129 return m_cxxConstructorDecl
->getNameAsString();
133 CXXMethodDecl
const* getAsCXXMethodDecl() const
135 if (m_calleeFunctionDecl
)
136 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
141 class UnusedVarsGlobal
: public loplugin::FilteringPlugin
<UnusedVarsGlobal
>
144 explicit UnusedVarsGlobal(loplugin::InstantiationData
const& data
)
145 : FilteringPlugin(data
)
149 virtual void run() override
;
151 bool shouldVisitTemplateInstantiations() const { return true; }
152 bool shouldVisitImplicitCode() const { return true; }
154 bool VisitVarDecl(const VarDecl
*);
155 bool VisitDeclRefExpr(const DeclRefExpr
*);
156 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
157 bool TraverseFunctionDecl(FunctionDecl
*);
158 bool TraverseIfStmt(IfStmt
*);
161 MyVarInfo
niceName(const VarDecl
*);
162 void checkIfReadFrom(const VarDecl
* fieldDecl
, const DeclRefExpr
* declRefExpr
);
163 void checkIfWrittenTo(const VarDecl
* fieldDecl
, const DeclRefExpr
* declRefExpr
);
164 bool isSomeKindOfZero(const Expr
* arg
);
165 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
);
166 bool IsPassedByNonConst(const VarDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
167 CalleeWrapper calleeFunctionDecl
);
168 compat::optional
<CalleeWrapper
> getCallee(CallExpr
const*);
170 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
171 // we store the parent function on the way down the AST.
172 FunctionDecl
* insideFunctionDecl
= nullptr;
173 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
176 void UnusedVarsGlobal::run()
178 handler
.enableTreeWideAnalysisMode();
180 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
182 if (!isUnitTestMode())
184 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
185 // writing to the same logfile
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
194 std::ofstream myfile
;
195 myfile
.open(WORKDIR
"/loplugin.unusedvarsglobal.log", std::ios::app
| std::ios::out
);
201 for (const MyVarInfo
& s
: readFromSet
)
202 report(DiagnosticsEngine::Warning
, "read", s
.varDecl
->getBeginLoc());
203 for (const MyVarInfo
& s
: writeToSet
)
204 report(DiagnosticsEngine::Warning
, "write", s
.varDecl
->getBeginLoc());
208 MyVarInfo
UnusedVarsGlobal::niceName(const VarDecl
* varDecl
)
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
);
226 = std::string(name
.substr(strlen(SRCDIR
) + 1)) + ":"
227 + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
228 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
233 bool UnusedVarsGlobal::VisitVarDecl(const VarDecl
* varDecl
)
235 varDecl
= varDecl
->getCanonicalDecl();
236 if (isa
<ParmVarDecl
>(varDecl
))
238 if (!varDecl
->hasGlobalStorage())
240 if (!varDecl
->getLocation().isValid() || ignoreLocation(varDecl
))
242 // ignore stuff that forms part of the stable URE interface
243 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
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())
257 auto initExpr
= varDecl
->getAnyInitializer();
258 if (initExpr
&& !isSomeKindOfZero(initExpr
))
259 writeToSet
.insert(niceName(varDecl
));
261 definitionSet
.insert(niceName(varDecl
));
265 bool UnusedVarsGlobal::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
267 const Decl
* decl
= declRefExpr
->getDecl();
268 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(decl
);
271 if (isa
<ParmVarDecl
>(varDecl
))
273 if (!varDecl
->hasGlobalStorage())
275 varDecl
= varDecl
->getCanonicalDecl();
276 if (!varDecl
->getLocation().isValid() || ignoreLocation(varDecl
))
278 // ignore stuff that forms part of the stable URE interface
279 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
281 checkIfReadFrom(varDecl
, declRefExpr
);
282 checkIfWrittenTo(varDecl
, declRefExpr
);
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
)
293 if (arg
->isValueDependent())
295 if (arg
->getType().isNull())
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
))
303 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(arg
))
304 return cxxConstructExpr
->getConstructor()->isDefaultConstructor();
306 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
308 if (isa
<CXXNullPtrLiteralExpr
>(arg
))
310 if (isa
<MaterializeTemporaryExpr
>(arg
))
312 const CXXBindTemporaryExpr
* strippedArg
313 = dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
316 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
317 if (temp
->getNumArgs() == 0)
319 if (loplugin::TypeCheck(temp
->getType())
324 if (loplugin::TypeCheck(temp
->getType())
334 // Get the expression contents.
335 // This helps us find params which are always initialised with something like "OUString()".
336 SourceManager
& SM
= compiler
.getSourceManager();
337 SourceLocation startLoc
= arg
->getBeginLoc();
338 SourceLocation endLoc
= arg
->getEndLoc();
339 const char* p1
= SM
.getCharacterData(startLoc
);
340 const char* p2
= SM
.getCharacterData(endLoc
);
341 if (!p1
|| !p2
|| (p2
- p1
) < 0 || (p2
- p1
) > 40)
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()")
353 else if (s
== "OString()")
358 static char easytolower(char in
)
360 if (in
<= 'Z' && in
>= 'A')
361 return in
- ('Z' - 'z');
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
;
379 bool UnusedVarsGlobal::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
381 auto copy2
= insideFunctionDecl
;
382 insideFunctionDecl
= functionDecl
;
383 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
384 insideFunctionDecl
= copy2
;
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
);
412 insideConditionalCheckOfVarSet
.pop_back();
416 void UnusedVarsGlobal::checkIfReadFrom(const VarDecl
* varDecl
, const DeclRefExpr
* declRefExpr
)
418 auto parentsRange
= compiler
.getASTContext().getParents(*declRefExpr
);
419 const Stmt
* child
= declRefExpr
;
421 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
422 // walk up the tree until we find something interesting
423 bool bPotentiallyReadFrom
= false;
425 auto walkUp
= [&]() {
427 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
428 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
429 : parentsRange
.begin()->get
<Stmt
>();
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
)
447 if (isa
<CXXReinterpretCastExpr
>(parent
))
449 // once we see one of these, there is not much useful we can know
450 bPotentiallyReadFrom
= true;
453 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
454 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
455 || isa
<ExprWithCleanups
>(parent
))
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;
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.
479 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
481 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
484 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
486 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
489 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
491 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
494 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
496 if (arraySubscriptExpr
->getIdx() == child
)
498 bPotentiallyReadFrom
= true;
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
)
515 bPotentiallyReadFrom
= true;
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
)
529 else if (op
== OO_GreaterGreaterEqual
&& operatorCallExpr
->getArg(1) == child
)
530 break; // this is a write-only call
533 bPotentiallyReadFrom
= true;
537 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
539 bool bWriteOnlyCall
= false;
540 const CXXMethodDecl
* callee
= cxxMemberCallExpr
->getMethodDecl();
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;
569 bPotentiallyReadFrom
= true;
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
);
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;
588 bPotentiallyReadFrom
= true;
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;
601 else if (isa
<CXXDeleteExpr
>(parent
) || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
602 || isa
<CXXUnresolvedConstructExpr
>(parent
) || isa
<CompoundStmt
>(parent
)
603 || isa
<LabelStmt
>(parent
) || isa
<CXXForRangeStmt
>(parent
)
604 || isa
<CXXTypeidExpr
>(parent
) || isa
<DefaultStmt
>(parent
)
605 || isa
<GCCAsmStmt
>(parent
) || isa
<LambdaExpr
>(parent
) // TODO
606 || isa
<CXXDefaultArgExpr
>(parent
) || isa
<AtomicExpr
>(parent
)
607 || isa
<VAArgExpr
>(parent
) || isa
<DeclRefExpr
>(parent
) || isa
<ConstantExpr
>(parent
)
608 || isa
<SubstNonTypeTemplateParmExpr
>(parent
))
614 bPotentiallyReadFrom
= true;
622 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be?",
623 declRefExpr
->getBeginLoc())
624 << declRefExpr
->getSourceRange();
625 report(DiagnosticsEngine::Note
, "parent over here", parent
->getBeginLoc())
626 << parent
->getSourceRange();
631 if (bPotentiallyReadFrom
)
632 readFromSet
.insert(niceName(varDecl
));
635 void UnusedVarsGlobal::checkIfWrittenTo(const VarDecl
* varDecl
, const DeclRefExpr
* declRefExpr
)
637 // if we're inside a block that looks like
640 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
641 if (std::find(insideConditionalCheckOfVarSet
.begin(), insideConditionalCheckOfVarSet
.end(),
643 != insideConditionalCheckOfVarSet
.end())
646 auto parentsRange
= compiler
.getASTContext().getParents(*declRefExpr
);
647 const Stmt
* child
= declRefExpr
;
649 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
650 // walk up the tree until we find something interesting
651 bool bPotentiallyWrittenTo
= false;
653 auto walkUp
= [&]() {
655 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
656 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
657 : parentsRange
.begin()->get
<Stmt
>();
663 // check if we have an expression like
665 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
666 if (parentsRange
.begin() != parentsRange
.end())
668 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
669 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
670 // which is of type 'T&&' and also an l-value-ref ?
671 if (varDecl
&& !varDecl
->isImplicit()
672 && loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
674 bPotentiallyWrittenTo
= true;
679 if (isa
<CXXReinterpretCastExpr
>(parent
))
681 // once we see one of these, there is not much useful we can know
682 bPotentiallyWrittenTo
= true;
685 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
686 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
687 || isa
<ExprWithCleanups
>(parent
))
691 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
693 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
694 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
697 bPotentiallyWrittenTo
= true;
701 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
703 if (arraySubscriptExpr
->getIdx() == child
)
707 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
709 auto callee
= getCallee(operatorCallExpr
);
712 // if calling a non-const operator on the field
713 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
714 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
)
716 if (!calleeMethodDecl
->isConst())
717 bPotentiallyWrittenTo
718 = checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
720 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
722 bPotentiallyWrittenTo
= true;
726 bPotentiallyWrittenTo
= true; // conservative, could improve
729 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
731 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
732 if (calleeMethodDecl
)
734 // if calling a non-const method on the field
735 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
736 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
738 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
740 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
742 if (!calleeMethodDecl
->isConst())
743 bPotentiallyWrittenTo
744 = checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
747 else if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
748 CalleeWrapper(calleeMethodDecl
)))
749 bPotentiallyWrittenTo
= true;
752 bPotentiallyWrittenTo
= true; // can happen in templates
755 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
757 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
758 CalleeWrapper(cxxConstructExpr
)))
759 bPotentiallyWrittenTo
= true;
762 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
764 auto callee
= getCallee(callExpr
);
767 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
768 bPotentiallyWrittenTo
= true;
771 bPotentiallyWrittenTo
= true; // conservative, could improve
774 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
776 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
777 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
778 || op
== BO_RemAssign
|| op
== BO_AddAssign
779 || op
== BO_SubAssign
|| op
== BO_ShlAssign
780 || op
== BO_ShrAssign
|| op
== BO_AndAssign
781 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
784 if (binaryOp
->getLHS() == child
)
785 bPotentiallyWrittenTo
= true;
786 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
789 // if the LHS is a non-const reference, we could write to the field later on
790 bPotentiallyWrittenTo
= true;
794 else if (isa
<ReturnStmt
>(parent
))
796 if (insideFunctionDecl
)
798 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
799 if (tc
.LvalueReference().NonConst())
800 bPotentiallyWrittenTo
= true;
804 else if (isa
<ConditionalOperator
>(parent
) || isa
<SwitchStmt
>(parent
)
805 || isa
<DeclStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<CXXNewExpr
>(parent
)
806 || isa
<ForStmt
>(parent
) || isa
<InitListExpr
>(parent
)
807 || isa
<CXXDependentScopeMemberExpr
>(parent
) || isa
<UnresolvedMemberExpr
>(parent
)
808 || isa
<MaterializeTemporaryExpr
>(parent
) || isa
<IfStmt
>(parent
)
809 || isa
<DoStmt
>(parent
) || isa
<CXXDeleteExpr
>(parent
)
810 || isa
<UnaryExprOrTypeTraitExpr
>(parent
) || isa
<CXXUnresolvedConstructExpr
>(parent
)
811 || isa
<CompoundStmt
>(parent
) || isa
<LabelStmt
>(parent
)
812 || isa
<CXXForRangeStmt
>(parent
) || isa
<CXXTypeidExpr
>(parent
)
813 || isa
<DefaultStmt
>(parent
) || isa
<GCCAsmStmt
>(parent
) || isa
<ConstantExpr
>(parent
)
814 || isa
<AtomicExpr
>(parent
) || isa
<CXXDefaultArgExpr
>(parent
)
815 || isa
<VAArgExpr
>(parent
) || isa
<DeclRefExpr
>(parent
)
816 || isa
<SubstNonTypeTemplateParmExpr
>(parent
) || isa
<LambdaExpr
>(parent
)) // TODO
822 bPotentiallyWrittenTo
= true;
830 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
831 declRefExpr
->getBeginLoc())
832 << bPotentiallyWrittenTo
<< declRefExpr
->getSourceRange();
835 report(DiagnosticsEngine::Note
, "parent over here", parent
->getBeginLoc())
836 << parent
->getSourceRange();
840 varDecl
->getType()->dump();
843 if (bPotentiallyWrittenTo
)
844 writeToSet
.insert(niceName(varDecl
));
847 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
848 bool UnusedVarsGlobal::checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
)
850 auto const tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
851 bool listLike
= false, setLike
= false, mapLike
= false, cssSequence
= false;
852 if (tc
.Class("deque").StdNamespace() || tc
.Class("list").StdNamespace()
853 || tc
.Class("queue").StdNamespace() || tc
.Class("vector").StdNamespace())
857 else if (tc
.Class("set").StdNamespace() || tc
.Class("unordered_set").StdNamespace())
861 else if (tc
.Class("map").StdNamespace() || tc
.Class("unordered_map").StdNamespace())
865 else if (tc
.Class("Sequence")
877 if (calleeMethodDecl
->isOverloadedOperator())
879 auto oo
= calleeMethodDecl
->getOverloadedOperator();
882 // This is operator[]. We only care about things that add elements to the collection.
883 // if nothing modifies the size of the collection, then nothing useful
890 auto name
= calleeMethodDecl
->getName();
891 if (listLike
|| setLike
|| mapLike
)
893 if (name
== "reserve" || name
== "shrink_to_fit" || name
== "clear" || name
== "erase"
894 || name
== "pop_back" || name
== "pop_front" || name
== "front" || name
== "back"
895 || name
== "data" || name
== "remove" || name
== "remove_if" || name
== "unique"
896 || name
== "sort" || name
== "begin" || name
== "end" || name
== "rbegin"
897 || name
== "rend" || name
== "at" || name
== "find" || name
== "equal_range"
898 || name
== "lower_bound" || name
== "upper_bound")
903 if (name
== "getArray" || name
== "begin" || name
== "end")
910 bool UnusedVarsGlobal::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
911 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
913 unsigned len
= std::min(callExpr
.getNumArgs(), calleeFunctionDecl
.getNumParams());
914 // if it's an array, passing it by value to a method typically means the
915 // callee takes a pointer and can modify the array
916 if (varDecl
->getType()->isConstantArrayType())
918 for (unsigned i
= 0; i
< len
; ++i
)
919 if (callExpr
.getArg(i
) == child
)
920 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
925 for (unsigned i
= 0; i
< len
; ++i
)
926 if (callExpr
.getArg(i
) == child
)
927 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
935 compat::optional
<CalleeWrapper
> UnusedVarsGlobal::getCallee(CallExpr
const* callExpr
)
937 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
939 return CalleeWrapper(functionDecl
);
941 // Extract the functionprototype from a type
942 clang::Type
const* calleeType
= callExpr
->getCallee()->getType().getTypePtr();
943 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>())
945 if (auto prototype
= pointerType
->getPointeeType()
946 ->getUnqualifiedDesugaredType()
947 ->getAs
<FunctionProtoType
>())
949 return CalleeWrapper(prototype
);
953 return compat::optional
<CalleeWrapper
>();
956 loplugin::Plugin::Registration
<UnusedVarsGlobal
> X("unusedvarsglobal", false);
961 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */