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>
25 #include "clang/AST/ParentMapContext.h"
28 Look for static vars that are only assigned to once, and never written to, they can be const.
34 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
38 const CallExpr
* m_callExpr
;
39 const CXXConstructExpr
* m_cxxConstructExpr
;
42 CallerWrapper(const CallExpr
* callExpr
)
43 : m_callExpr(callExpr
)
44 , m_cxxConstructExpr(nullptr)
47 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
49 , m_cxxConstructExpr(cxxConstructExpr
)
52 unsigned getNumArgs() const
54 return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs();
56 const Expr
* getArg(unsigned i
) const
58 return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
);
63 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
64 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
65 const FunctionProtoType
* m_functionPrototype
= nullptr;
68 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
)
69 : m_calleeFunctionDecl(calleeFunctionDecl
)
72 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
)
73 : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor())
76 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
)
77 : m_functionPrototype(functionPrototype
)
80 unsigned getNumParams() const
82 if (m_calleeFunctionDecl
)
83 return m_calleeFunctionDecl
->getNumParams();
84 else if (m_cxxConstructorDecl
)
85 return m_cxxConstructorDecl
->getNumParams();
86 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
87 // FunctionProtoType will assert if we call getParamTypes() and it has no params
90 return m_functionPrototype
->getParamTypes().size();
92 const QualType
getParamType(unsigned i
) const
94 if (m_calleeFunctionDecl
)
95 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
96 else if (m_cxxConstructorDecl
)
97 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
99 return m_functionPrototype
->getParamTypes()[i
];
101 std::string
getNameAsString() const
103 if (m_calleeFunctionDecl
)
104 return m_calleeFunctionDecl
->getNameAsString();
105 else if (m_cxxConstructorDecl
)
106 return m_cxxConstructorDecl
->getNameAsString();
110 CXXMethodDecl
const* getAsCXXMethodDecl() const
112 if (m_calleeFunctionDecl
)
113 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
118 class ConstVars
: public RecursiveASTVisitor
<ConstVars
>, public loplugin::Plugin
121 explicit ConstVars(loplugin::InstantiationData
const& data
)
126 virtual void run() override
;
128 bool shouldVisitTemplateInstantiations() const { return true; }
129 bool shouldVisitImplicitCode() const { return true; }
131 bool VisitVarDecl(const VarDecl
*);
132 bool VisitCXXForRangeStmt(const CXXForRangeStmt
*);
133 bool VisitDeclRefExpr(const DeclRefExpr
*);
134 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
135 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
136 bool TraverseFunctionDecl(FunctionDecl
*);
137 bool TraverseIfStmt(IfStmt
*);
140 void check(const VarDecl
* varDecl
, const Expr
* memberExpr
);
141 bool isSomeKindOfZero(const Expr
* arg
);
142 bool IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
, CallerWrapper callExpr
,
143 CalleeWrapper calleeFunctionDecl
);
144 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const*);
146 RecordDecl
* insideMoveOrCopyDeclParent
= nullptr;
147 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
148 // we store the parent function on the way down the AST.
149 FunctionDecl
* insideFunctionDecl
= nullptr;
150 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
152 std::set
<VarDecl
const*> cannotBeConstSet
;
153 std::set
<VarDecl
const*> definitionSet
;
156 void ConstVars::run()
158 // clang::Expr::isCXX11ConstantExpr only works for C++
159 if (!compiler
.getLangOpts().CPlusPlus
)
162 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
164 SourceManager
& SM
= compiler
.getSourceManager();
165 for (VarDecl
const* v
: definitionSet
)
167 if (cannotBeConstSet
.find(v
) != cannotBeConstSet
.end())
169 llvm::StringRef
sourceString(SM
.getCharacterData(v
->getSourceRange().getEnd()), 50);
170 // Implement a marker that disables this plugins warning at a specific site
171 if (sourceString
.contains("loplugin:constvars:ignore"))
173 report(DiagnosticsEngine::Warning
, "var can be const", v
->getBeginLoc());
177 bool ConstVars::VisitVarDecl(const VarDecl
* varDecl
)
179 varDecl
= varDecl
->getCanonicalDecl();
180 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
182 if (!varDecl
->hasGlobalStorage())
184 if (isa
<ParmVarDecl
>(varDecl
))
186 if (varDecl
->getLinkageAndVisibility().getLinkage() == ExternalLinkage
)
188 if (varDecl
->getType().isConstQualified())
190 if (isa
<ConstantArrayType
>(varDecl
->getType()))
192 if (loplugin::TypeCheck(varDecl
->getType()).Pointer().Const())
194 // ignore stuff that forms part of the stable URE interface
195 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
198 if (!varDecl
->getInit())
200 if (varDecl
->getInit()->isInstantiationDependent())
202 if (!varDecl
->getInit()->isCXX11ConstantExpr(compiler
.getASTContext()))
205 definitionSet
.insert(varDecl
);
209 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt
* forStmt
)
211 if (forStmt
->getBeginLoc().isValid() && ignoreLocation(forStmt
))
213 const VarDecl
* varDecl
= forStmt
->getLoopVariable();
216 // we don't handle structured assignment properly
217 if (isa
<DecompositionDecl
>(varDecl
))
219 auto tc
= loplugin::TypeCheck(varDecl
->getType());
220 if (!tc
.LvalueReference())
222 if (tc
.LvalueReference().Const())
225 definitionSet
.insert(varDecl
);
229 bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
231 auto copy
= insideMoveOrCopyDeclParent
;
232 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
234 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
235 insideMoveOrCopyDeclParent
= cxxConstructorDecl
->getParent();
237 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
238 insideMoveOrCopyDeclParent
= copy
;
242 bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
244 auto copy1
= insideMoveOrCopyDeclParent
;
245 auto copy2
= insideFunctionDecl
;
246 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
248 if (cxxMethodDecl
->isCopyAssignmentOperator() || cxxMethodDecl
->isMoveAssignmentOperator())
249 insideMoveOrCopyDeclParent
= cxxMethodDecl
->getParent();
251 insideFunctionDecl
= cxxMethodDecl
;
252 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
253 insideMoveOrCopyDeclParent
= copy1
;
254 insideFunctionDecl
= copy2
;
258 bool ConstVars::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
260 auto copy2
= insideFunctionDecl
;
261 insideFunctionDecl
= functionDecl
;
262 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
263 insideFunctionDecl
= copy2
;
267 bool ConstVars::TraverseIfStmt(IfStmt
* ifStmt
)
269 VarDecl
const* varDecl
= nullptr;
270 if (Expr
const* cond
= ifStmt
->getCond())
272 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cond
->IgnoreParenImpCasts()))
274 if ((varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl())))
275 insideConditionalCheckOfVarSet
.push_back(varDecl
);
278 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
280 insideConditionalCheckOfVarSet
.pop_back();
284 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
286 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl());
289 varDecl
= varDecl
->getCanonicalDecl();
290 if (ignoreLocation(varDecl
))
292 // ignore stuff that forms part of the stable URE interface
293 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
296 if (definitionSet
.find(varDecl
) != definitionSet
.end())
297 check(varDecl
, declRefExpr
);
302 void ConstVars::check(const VarDecl
* varDecl
, const Expr
* memberExpr
)
304 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
305 const Stmt
* child
= memberExpr
;
307 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
309 // walk up the tree until we find something interesting
311 bool bCannotBeConst
= false;
313 auto walkUp
= [&]() {
315 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
316 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
317 : parentsRange
.begin()->get
<Stmt
>();
323 // check if we have an expression like
325 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
326 if (parentsRange
.begin() != parentsRange
.end())
328 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
331 if (varDecl
->isImplicit())
333 // so we can walk up from inside a for-range stmt
334 parentsRange
= compiler
.getASTContext().getParents(*varDecl
);
335 if (parentsRange
.begin() != parentsRange
.end())
336 parent
= parentsRange
.begin()->get
<Stmt
>();
338 else if (loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
340 bCannotBeConst
= true;
348 if (isa
<CXXReinterpretCastExpr
>(parent
))
350 // once we see one of these, there is not much useful we can know
351 bCannotBeConst
= true;
354 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
355 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
356 || isa
<ExprWithCleanups
>(parent
))
360 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
362 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
363 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
366 bCannotBeConst
= true;
370 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
372 auto callee
= getCallee(operatorCallExpr
);
375 // if calling a non-const operator on the var
376 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
377 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
378 && !calleeMethodDecl
->isConst())
380 bCannotBeConst
= true;
382 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
384 bCannotBeConst
= true;
388 bCannotBeConst
= true; // conservative, could improve
391 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
393 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
394 if (calleeMethodDecl
)
396 // if calling a non-const method on the var
397 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
398 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
400 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
402 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
403 && !calleeMethodDecl
->isConst())
405 bCannotBeConst
= true;
408 if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
409 CalleeWrapper(calleeMethodDecl
)))
410 bCannotBeConst
= true;
413 bCannotBeConst
= true; // can happen in templates
416 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
418 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
419 CalleeWrapper(cxxConstructExpr
)))
420 bCannotBeConst
= true;
423 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
425 auto callee
= getCallee(callExpr
);
428 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
429 bCannotBeConst
= true;
432 bCannotBeConst
= true; // conservative, could improve
435 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
437 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
438 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
439 || op
== BO_RemAssign
|| op
== BO_AddAssign
440 || op
== BO_SubAssign
|| op
== BO_ShlAssign
441 || op
== BO_ShrAssign
|| op
== BO_AndAssign
442 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
445 if (binaryOp
->getLHS() == child
)
446 bCannotBeConst
= true;
447 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
450 // if the LHS is a non-const reference, we could write to the var later on
451 bCannotBeConst
= true;
455 else if (isa
<ReturnStmt
>(parent
))
457 if (insideFunctionDecl
)
459 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
460 if (tc
.LvalueReference().NonConst())
461 bCannotBeConst
= true;
465 else if (auto rangeStmt
= dyn_cast
<CXXForRangeStmt
>(parent
))
467 if (rangeStmt
->getRangeStmt() == child
)
469 auto tc
= loplugin::TypeCheck(rangeStmt
->getLoopVariable()->getType());
470 if (tc
.LvalueReference().NonConst())
471 bCannotBeConst
= true;
475 else if (isa
<SwitchStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<ForStmt
>(parent
)
476 || isa
<IfStmt
>(parent
) || isa
<DoStmt
>(parent
) || isa
<DefaultStmt
>(parent
))
488 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
489 memberExpr
->getBeginLoc())
490 << bCannotBeConst
<< memberExpr
->getSourceRange();
493 report(DiagnosticsEngine::Note
, "parent over here", parent
->getBeginLoc())
494 << parent
->getSourceRange();
498 varDecl
->getType()->dump();
502 cannotBeConstSet
.insert(varDecl
);
505 bool ConstVars::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
506 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
508 unsigned len
= std::min(callExpr
.getNumArgs(), calleeFunctionDecl
.getNumParams());
509 // if it's an array, passing it by value to a method typically means the
510 // callee takes a pointer and can modify the array
511 if (varDecl
->getType()->isConstantArrayType())
513 for (unsigned i
= 0; i
< len
; ++i
)
514 if (callExpr
.getArg(i
) == child
)
515 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
520 for (unsigned i
= 0; i
< len
; ++i
)
521 if (callExpr
.getArg(i
) == child
)
523 auto tc
= loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
));
524 if (tc
.LvalueReference().NonConst() || tc
.Pointer().NonConst())
531 llvm::Optional
<CalleeWrapper
> ConstVars::getCallee(CallExpr
const* callExpr
)
533 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
535 return CalleeWrapper(functionDecl
);
537 // Extract the functionprototype from a type
538 clang::Type
const* calleeType
= callExpr
->getCallee()->getType().getTypePtr();
539 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>())
541 if (auto prototype
= pointerType
->getPointeeType()
542 ->getUnqualifiedDesugaredType()
543 ->getAs
<FunctionProtoType
>())
545 return CalleeWrapper(prototype
);
549 return llvm::Optional
<CalleeWrapper
>();
552 /** off by default because it is very expensive, it walks up the AST a lot */
553 loplugin::Plugin::Registration
<ConstVars
> X("constvars", false);
558 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */