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 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
33 This performs two analyses:
34 (1) look for unused global vars
35 (2) look for global vars that are write-only
42 const VarDecl
* varDecl
;
43 std::string fieldName
;
44 std::string fieldType
;
45 std::string sourceLocation
;
47 bool operator<(const MyVarInfo
& lhs
, const MyVarInfo
& rhs
)
49 return std::tie(lhs
.sourceLocation
, lhs
.fieldName
)
50 < std::tie(rhs
.sourceLocation
, rhs
.fieldName
);
53 // try to limit the voluminous output a little
54 static std::set
<MyVarInfo
> readFromSet
;
55 static std::set
<MyVarInfo
> writeToSet
;
56 static std::set
<MyVarInfo
> definitionSet
;
59 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
63 const CallExpr
* m_callExpr
;
64 const CXXConstructExpr
* m_cxxConstructExpr
;
67 CallerWrapper(const CallExpr
* callExpr
)
68 : m_callExpr(callExpr
)
69 , m_cxxConstructExpr(nullptr)
72 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
74 , m_cxxConstructExpr(cxxConstructExpr
)
77 unsigned getNumArgs() const
79 return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs();
81 const Expr
* getArg(unsigned i
) const
83 return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
);
88 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
89 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
90 const FunctionProtoType
* m_functionPrototype
= nullptr;
93 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
)
94 : m_calleeFunctionDecl(calleeFunctionDecl
)
97 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
)
98 : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor())
101 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
)
102 : m_functionPrototype(functionPrototype
)
105 unsigned getNumParams() const
107 if (m_calleeFunctionDecl
)
108 return m_calleeFunctionDecl
->getNumParams();
109 else if (m_cxxConstructorDecl
)
110 return m_cxxConstructorDecl
->getNumParams();
111 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
112 // FunctionProtoType will assert if we call getParamTypes() and it has no params
115 return m_functionPrototype
->getParamTypes().size();
117 const QualType
getParamType(unsigned i
) const
119 if (m_calleeFunctionDecl
)
120 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
121 else if (m_cxxConstructorDecl
)
122 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
124 return m_functionPrototype
->getParamTypes()[i
];
126 std::string
getNameAsString() const
128 if (m_calleeFunctionDecl
)
129 return m_calleeFunctionDecl
->getNameAsString();
130 else if (m_cxxConstructorDecl
)
131 return m_cxxConstructorDecl
->getNameAsString();
135 CXXMethodDecl
const* getAsCXXMethodDecl() const
137 if (m_calleeFunctionDecl
)
138 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
143 class UnusedVarsGlobal
: public loplugin::FilteringPlugin
<UnusedVarsGlobal
>
146 explicit UnusedVarsGlobal(loplugin::InstantiationData
const& data
)
147 : FilteringPlugin(data
)
151 virtual void run() override
;
153 bool shouldVisitTemplateInstantiations() const { return true; }
154 bool shouldVisitImplicitCode() const { return true; }
156 bool VisitVarDecl(const VarDecl
*);
157 bool VisitDeclRefExpr(const DeclRefExpr
*);
158 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
159 bool TraverseFunctionDecl(FunctionDecl
*);
160 bool TraverseIfStmt(IfStmt
*);
163 MyVarInfo
niceName(const VarDecl
*);
164 void checkIfReadFrom(const VarDecl
* fieldDecl
, const DeclRefExpr
* declRefExpr
);
165 void checkIfWrittenTo(const VarDecl
* fieldDecl
, const DeclRefExpr
* declRefExpr
);
166 bool isSomeKindOfZero(const Expr
* arg
);
167 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
);
168 bool IsPassedByNonConst(const VarDecl
* fieldDecl
, const Stmt
* child
, CallerWrapper callExpr
,
169 CalleeWrapper calleeFunctionDecl
);
170 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const*);
172 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
173 // we store the parent function on the way down the AST.
174 FunctionDecl
* insideFunctionDecl
= nullptr;
175 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
178 void UnusedVarsGlobal::run()
180 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
182 if (!isUnitTestMode())
184 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
185 // writing to the same logfile
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", compat::getBeginLoc(s
.varDecl
));
203 for (const MyVarInfo
& s
: writeToSet
)
204 report(DiagnosticsEngine::Warning
, "write", compat::getBeginLoc(s
.varDecl
));
208 MyVarInfo
UnusedVarsGlobal::niceName(const VarDecl
* varDecl
)
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
= 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)
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()")
355 else if (s
== "aEmptyOUStr") //sw
357 else if (s
== "EMPTY_OUSTRING") //sc
359 else if (s
== "GetEmptyOUString()") //sc
364 static char easytolower(char in
)
366 if (in
<= 'Z' && in
>= 'A')
367 return in
- ('Z' - 'z');
371 bool startswith(const std::string
& rStr
, const char* pSubStr
)
373 return rStr
.compare(0, strlen(pSubStr
), pSubStr
) == 0;
376 bool UnusedVarsGlobal::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
378 auto copy2
= insideFunctionDecl
;
379 insideFunctionDecl
= cxxMethodDecl
;
380 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
381 insideFunctionDecl
= copy2
;
385 bool UnusedVarsGlobal::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
387 auto copy2
= insideFunctionDecl
;
388 insideFunctionDecl
= functionDecl
;
389 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
390 insideFunctionDecl
= copy2
;
394 bool UnusedVarsGlobal::TraverseIfStmt(IfStmt
* ifStmt
)
396 VarDecl
const* varDecl
= nullptr;
397 Expr
const* cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
399 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(cond
))
401 if (auto cxxConvert
= dyn_cast_or_null
<CXXConversionDecl
>(memberCallExpr
->getMethodDecl()))
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
);
410 else if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cond
))
412 if ((varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl())))
413 insideConditionalCheckOfVarSet
.push_back(varDecl
);
416 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
418 insideConditionalCheckOfVarSet
.pop_back();
422 void UnusedVarsGlobal::checkIfReadFrom(const VarDecl
* varDecl
, const DeclRefExpr
* declRefExpr
)
424 auto parentsRange
= compiler
.getASTContext().getParents(*declRefExpr
);
425 const Stmt
* child
= declRefExpr
;
427 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
428 // walk up the tree until we find something interesting
429 bool bPotentiallyReadFrom
= false;
431 auto walkUp
= [&]() {
433 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
434 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
435 : parentsRange
.begin()->get
<Stmt
>();
441 // check if we're inside a CXXCtorInitializer or a VarDecl
442 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
443 if (parentsRange
.begin() != parentsRange
.end())
445 const Decl
* decl
= parentsRange
.begin()->get
<Decl
>();
446 if (decl
&& (isa
<CXXConstructorDecl
>(decl
) || isa
<VarDecl
>(decl
)))
447 bPotentiallyReadFrom
= true;
449 if (!bPotentiallyReadFrom
)
453 if (isa
<CXXReinterpretCastExpr
>(parent
))
455 // once we see one of these, there is not much useful we can know
456 bPotentiallyReadFrom
= true;
459 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
460 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
461 || isa
<ExprWithCleanups
>(parent
))
465 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
467 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
468 if (declRefExpr
->getType()->isArrayType() && op
== UO_Deref
)
470 // ignore, deref'ing an array does not count as a read
472 else if (op
== UO_AddrOf
|| op
== UO_Deref
|| op
== UO_Plus
|| op
== UO_Minus
473 || op
== UO_Not
|| op
== UO_LNot
)
475 bPotentiallyReadFrom
= true;
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.
485 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
487 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
490 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
492 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
495 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
497 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
500 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
502 if (arraySubscriptExpr
->getIdx() == child
)
504 bPotentiallyReadFrom
= true;
509 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
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
)
521 bPotentiallyReadFrom
= true;
525 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
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
)
535 else if (op
== OO_GreaterGreaterEqual
&& operatorCallExpr
->getArg(1) == child
)
536 break; // this is a write-only call
539 bPotentiallyReadFrom
= true;
543 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
545 bool bWriteOnlyCall
= false;
546 const CXXMethodDecl
* callee
= cxxMemberCallExpr
->getMethodDecl();
549 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
550 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
552 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
554 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
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;
575 bPotentiallyReadFrom
= true;
578 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
580 bool bWriteOnlyCall
= false;
581 // check for calls to ReadXXX(foo) type methods, where foo is write-only
582 auto callee
= getCallee(callExpr
);
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;
594 bPotentiallyReadFrom
= true;
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
))
604 bPotentiallyReadFrom
= true;
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
)
617 || isa
<SubstNonTypeTemplateParmExpr
>(parent
))
623 bPotentiallyReadFrom
= true;
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();
640 if (bPotentiallyReadFrom
)
641 readFromSet
.insert(niceName(varDecl
));
644 void UnusedVarsGlobal::checkIfWrittenTo(const VarDecl
* varDecl
, const DeclRefExpr
* declRefExpr
)
646 // if we're inside a block that looks like
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(),
652 != insideConditionalCheckOfVarSet
.end())
655 auto parentsRange
= compiler
.getASTContext().getParents(*declRefExpr
);
656 const Stmt
* child
= declRefExpr
;
658 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
659 // walk up the tree until we find something interesting
660 bool bPotentiallyWrittenTo
= false;
662 auto walkUp
= [&]() {
664 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
665 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
666 : parentsRange
.begin()->get
<Stmt
>();
672 // check if we have an expression like
674 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
675 if (parentsRange
.begin() != parentsRange
.end())
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())
683 bPotentiallyWrittenTo
= true;
688 if (isa
<CXXReinterpretCastExpr
>(parent
))
690 // once we see one of these, there is not much useful we can know
691 bPotentiallyWrittenTo
= true;
694 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
695 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
696 || isa
<ExprWithCleanups
>(parent
))
700 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
702 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
703 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
706 bPotentiallyWrittenTo
= true;
710 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
712 if (arraySubscriptExpr
->getIdx() == child
)
716 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
718 auto callee
= getCallee(operatorCallExpr
);
721 // if calling a non-const operator on the field
722 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
723 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
)
725 if (!calleeMethodDecl
->isConst())
726 bPotentiallyWrittenTo
727 = checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
729 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
731 bPotentiallyWrittenTo
= true;
735 bPotentiallyWrittenTo
= true; // conservative, could improve
738 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
740 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
741 if (calleeMethodDecl
)
743 // if calling a non-const method on the field
744 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
745 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
747 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
749 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
)
751 if (!calleeMethodDecl
->isConst())
752 bPotentiallyWrittenTo
753 = checkForWriteWhenUsingCollectionType(calleeMethodDecl
);
756 else if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
757 CalleeWrapper(calleeMethodDecl
)))
758 bPotentiallyWrittenTo
= true;
761 bPotentiallyWrittenTo
= true; // can happen in templates
764 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
766 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
767 CalleeWrapper(cxxConstructExpr
)))
768 bPotentiallyWrittenTo
= true;
771 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
773 auto callee
= getCallee(callExpr
);
776 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
777 bPotentiallyWrittenTo
= true;
780 bPotentiallyWrittenTo
= true; // conservative, could improve
783 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
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
;
793 if (binaryOp
->getLHS() == child
)
794 bPotentiallyWrittenTo
= true;
795 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
798 // if the LHS is a non-const reference, we could write to the field later on
799 bPotentiallyWrittenTo
= true;
803 else if (isa
<ReturnStmt
>(parent
))
805 if (insideFunctionDecl
)
807 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
808 if (tc
.LvalueReference().NonConst())
809 bPotentiallyWrittenTo
= true;
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
)
826 || isa
<AtomicExpr
>(parent
) || isa
<CXXDefaultArgExpr
>(parent
)
827 || isa
<VAArgExpr
>(parent
) || isa
<DeclRefExpr
>(parent
)
828 || isa
<SubstNonTypeTemplateParmExpr
>(parent
) || isa
<LambdaExpr
>(parent
)) // TODO
834 bPotentiallyWrittenTo
= true;
842 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
843 compat::getBeginLoc(declRefExpr
))
844 << bPotentiallyWrittenTo
<< declRefExpr
->getSourceRange();
847 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
848 << parent
->getSourceRange();
852 varDecl
->getType()->dump();
855 if (bPotentiallyWrittenTo
)
856 writeToSet
.insert(niceName(varDecl
));
859 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
860 bool UnusedVarsGlobal::checkForWriteWhenUsingCollectionType(const CXXMethodDecl
* calleeMethodDecl
)
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())
869 else if (tc
.Class("set").StdNamespace() || tc
.Class("unordered_set").StdNamespace())
873 else if (tc
.Class("map").StdNamespace() || tc
.Class("unordered_map").StdNamespace())
877 else if (tc
.Class("Sequence")
889 if (calleeMethodDecl
->isOverloadedOperator())
891 auto oo
= calleeMethodDecl
->getOverloadedOperator();
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
902 auto name
= calleeMethodDecl
->getName();
903 if (listLike
|| setLike
|| mapLike
)
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")
915 if (name
== "getArray" || name
== "begin" || name
== "end")
922 bool UnusedVarsGlobal::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
923 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
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())
930 for (unsigned i
= 0; i
< len
; ++i
)
931 if (callExpr
.getArg(i
) == child
)
932 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
937 for (unsigned i
= 0; i
< len
; ++i
)
938 if (callExpr
.getArg(i
) == child
)
939 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
947 llvm::Optional
<CalleeWrapper
> UnusedVarsGlobal::getCallee(CallExpr
const* callExpr
)
949 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
951 return CalleeWrapper(functionDecl
);
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
>())
957 if (auto prototype
= pointerType
->getPointeeType()
958 ->getUnqualifiedDesugaredType()
959 ->getAs
<FunctionProtoType
>())
961 return CalleeWrapper(prototype
);
965 return llvm::Optional
<CalleeWrapper
>();
968 loplugin::Plugin::Registration
<UnusedVarsGlobal
> X("unusedvarsglobal", false);
973 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */