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>
26 Look for static vars that are only assigned to once, and never written to, they can be const.
32 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
36 const CallExpr
* m_callExpr
;
37 const CXXConstructExpr
* m_cxxConstructExpr
;
40 CallerWrapper(const CallExpr
* callExpr
)
41 : m_callExpr(callExpr
)
42 , m_cxxConstructExpr(nullptr)
45 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
47 , m_cxxConstructExpr(cxxConstructExpr
)
50 unsigned getNumArgs() const
52 return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs();
54 const Expr
* getArg(unsigned i
) const
56 return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
);
61 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
62 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
63 const FunctionProtoType
* m_functionPrototype
= nullptr;
66 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
)
67 : m_calleeFunctionDecl(calleeFunctionDecl
)
70 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
)
71 : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor())
74 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
)
75 : m_functionPrototype(functionPrototype
)
78 unsigned getNumParams() const
80 if (m_calleeFunctionDecl
)
81 return m_calleeFunctionDecl
->getNumParams();
82 else if (m_cxxConstructorDecl
)
83 return m_cxxConstructorDecl
->getNumParams();
84 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
85 // FunctionProtoType will assert if we call getParamTypes() and it has no params
88 return m_functionPrototype
->getParamTypes().size();
90 const QualType
getParamType(unsigned i
) const
92 if (m_calleeFunctionDecl
)
93 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
94 else if (m_cxxConstructorDecl
)
95 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
97 return m_functionPrototype
->getParamTypes()[i
];
99 std::string
getNameAsString() const
101 if (m_calleeFunctionDecl
)
102 return m_calleeFunctionDecl
->getNameAsString();
103 else if (m_cxxConstructorDecl
)
104 return m_cxxConstructorDecl
->getNameAsString();
108 CXXMethodDecl
const* getAsCXXMethodDecl() const
110 if (m_calleeFunctionDecl
)
111 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
116 class ConstVars
: public RecursiveASTVisitor
<ConstVars
>, public loplugin::Plugin
119 explicit ConstVars(loplugin::InstantiationData
const& data
)
124 virtual void run() override
;
126 bool shouldVisitTemplateInstantiations() const { return true; }
127 bool shouldVisitImplicitCode() const { return true; }
129 bool VisitVarDecl(const VarDecl
*);
130 bool VisitCXXForRangeStmt(const CXXForRangeStmt
*);
131 bool VisitDeclRefExpr(const DeclRefExpr
*);
132 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
133 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
134 bool TraverseFunctionDecl(FunctionDecl
*);
135 bool TraverseIfStmt(IfStmt
*);
138 void check(const VarDecl
* varDecl
, const Expr
* memberExpr
);
139 bool isSomeKindOfZero(const Expr
* arg
);
140 bool IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
, CallerWrapper callExpr
,
141 CalleeWrapper calleeFunctionDecl
);
142 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const*);
144 RecordDecl
* insideMoveOrCopyDeclParent
= nullptr;
145 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
146 // we store the parent function on the way down the AST.
147 FunctionDecl
* insideFunctionDecl
= nullptr;
148 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
150 std::set
<VarDecl
const*> cannotBeConstSet
;
151 std::set
<VarDecl
const*> definitionSet
;
154 void ConstVars::run()
156 // clang::Expr::isCXX11ConstantExpr only works for C++
157 if (!compiler
.getLangOpts().CPlusPlus
)
160 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
162 SourceManager
& SM
= compiler
.getSourceManager();
163 for (VarDecl
const* v
: definitionSet
)
165 if (cannotBeConstSet
.find(v
) != cannotBeConstSet
.end())
167 llvm::StringRef
sourceString(SM
.getCharacterData(v
->getSourceRange().getEnd()), 50);
168 // Implement a marker that disables this plugins warning at a specific site
169 if (sourceString
.contains("loplugin:constvars:ignore"))
171 report(DiagnosticsEngine::Warning
, "var can be const", compat::getBeginLoc(v
));
175 bool ConstVars::VisitVarDecl(const VarDecl
* varDecl
)
177 varDecl
= varDecl
->getCanonicalDecl();
178 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
180 if (!varDecl
->hasGlobalStorage())
182 if (isa
<ParmVarDecl
>(varDecl
))
184 if (varDecl
->getLinkageAndVisibility().getLinkage() == ExternalLinkage
)
186 if (varDecl
->getType().isConstQualified())
188 if (isa
<ConstantArrayType
>(varDecl
->getType()))
190 if (loplugin::TypeCheck(varDecl
->getType()).Pointer().Const())
192 // ignore stuff that forms part of the stable URE interface
193 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
196 if (!varDecl
->getInit())
198 if (varDecl
->getInit()->isInstantiationDependent())
200 if (!varDecl
->getInit()->isCXX11ConstantExpr(compiler
.getASTContext()))
203 definitionSet
.insert(varDecl
);
207 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt
* forStmt
)
209 if (compat::getBeginLoc(forStmt
).isValid() && ignoreLocation(forStmt
))
211 const VarDecl
* varDecl
= forStmt
->getLoopVariable();
214 // we don't handle structured assignment properly
215 if (isa
<DecompositionDecl
>(varDecl
))
217 auto tc
= loplugin::TypeCheck(varDecl
->getType());
218 if (!tc
.LvalueReference())
220 if (tc
.LvalueReference().Const())
223 definitionSet
.insert(varDecl
);
227 bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
229 auto copy
= insideMoveOrCopyDeclParent
;
230 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
232 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
233 insideMoveOrCopyDeclParent
= cxxConstructorDecl
->getParent();
235 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
236 insideMoveOrCopyDeclParent
= copy
;
240 bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
242 auto copy1
= insideMoveOrCopyDeclParent
;
243 auto copy2
= insideFunctionDecl
;
244 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
246 if (cxxMethodDecl
->isCopyAssignmentOperator() || cxxMethodDecl
->isMoveAssignmentOperator())
247 insideMoveOrCopyDeclParent
= cxxMethodDecl
->getParent();
249 insideFunctionDecl
= cxxMethodDecl
;
250 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
251 insideMoveOrCopyDeclParent
= copy1
;
252 insideFunctionDecl
= copy2
;
256 bool ConstVars::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
258 auto copy2
= insideFunctionDecl
;
259 insideFunctionDecl
= functionDecl
;
260 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
261 insideFunctionDecl
= copy2
;
265 bool ConstVars::TraverseIfStmt(IfStmt
* ifStmt
)
267 VarDecl
const* varDecl
= nullptr;
268 Expr
const* cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
269 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cond
))
271 if ((varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl())))
272 insideConditionalCheckOfVarSet
.push_back(varDecl
);
274 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
276 insideConditionalCheckOfVarSet
.pop_back();
280 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
282 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl());
285 varDecl
= varDecl
->getCanonicalDecl();
286 if (ignoreLocation(varDecl
))
288 // ignore stuff that forms part of the stable URE interface
289 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
292 if (definitionSet
.find(varDecl
) != definitionSet
.end())
293 check(varDecl
, declRefExpr
);
298 void ConstVars::check(const VarDecl
* varDecl
, const Expr
* memberExpr
)
300 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
301 const Stmt
* child
= memberExpr
;
303 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
305 // walk up the tree until we find something interesting
307 bool bCannotBeConst
= false;
309 auto walkUp
= [&]() {
311 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
312 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
313 : parentsRange
.begin()->get
<Stmt
>();
319 // check if we have an expression like
321 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
322 if (parentsRange
.begin() != parentsRange
.end())
324 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
327 if (varDecl
->isImplicit())
329 // so we can walk up from inside a for-range stmt
330 parentsRange
= compiler
.getASTContext().getParents(*varDecl
);
331 if (parentsRange
.begin() != parentsRange
.end())
332 parent
= parentsRange
.begin()->get
<Stmt
>();
334 else if (loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
336 bCannotBeConst
= true;
344 if (isa
<CXXReinterpretCastExpr
>(parent
))
346 // once we see one of these, there is not much useful we can know
347 bCannotBeConst
= true;
350 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
351 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
352 || isa
<ExprWithCleanups
>(parent
))
356 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
358 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
359 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
362 bCannotBeConst
= true;
366 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
368 auto callee
= getCallee(operatorCallExpr
);
371 // if calling a non-const operator on the var
372 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
373 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
374 && !calleeMethodDecl
->isConst())
376 bCannotBeConst
= true;
378 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
380 bCannotBeConst
= true;
384 bCannotBeConst
= true; // conservative, could improve
387 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
389 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
390 if (calleeMethodDecl
)
392 // if calling a non-const method on the var
393 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
394 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
396 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
398 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
399 && !calleeMethodDecl
->isConst())
401 bCannotBeConst
= true;
404 if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
405 CalleeWrapper(calleeMethodDecl
)))
406 bCannotBeConst
= true;
409 bCannotBeConst
= true; // can happen in templates
412 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
414 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
415 CalleeWrapper(cxxConstructExpr
)))
416 bCannotBeConst
= true;
419 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
421 auto callee
= getCallee(callExpr
);
424 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
425 bCannotBeConst
= true;
428 bCannotBeConst
= true; // conservative, could improve
431 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
433 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
434 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
435 || op
== BO_RemAssign
|| op
== BO_AddAssign
436 || op
== BO_SubAssign
|| op
== BO_ShlAssign
437 || op
== BO_ShrAssign
|| op
== BO_AndAssign
438 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
441 if (binaryOp
->getLHS() == child
)
442 bCannotBeConst
= true;
443 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
446 // if the LHS is a non-const reference, we could write to the var later on
447 bCannotBeConst
= true;
451 else if (isa
<ReturnStmt
>(parent
))
453 if (insideFunctionDecl
)
455 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
456 if (tc
.LvalueReference().NonConst())
457 bCannotBeConst
= true;
461 else if (auto rangeStmt
= dyn_cast
<CXXForRangeStmt
>(parent
))
463 if (rangeStmt
->getRangeStmt() == child
)
465 auto tc
= loplugin::TypeCheck(rangeStmt
->getLoopVariable()->getType());
466 if (tc
.LvalueReference().NonConst())
467 bCannotBeConst
= true;
471 else if (isa
<SwitchStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<ForStmt
>(parent
)
472 || isa
<IfStmt
>(parent
) || isa
<DoStmt
>(parent
) || isa
<DefaultStmt
>(parent
))
484 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
485 compat::getBeginLoc(memberExpr
))
486 << bCannotBeConst
<< memberExpr
->getSourceRange();
489 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
490 << parent
->getSourceRange();
494 varDecl
->getType()->dump();
498 cannotBeConstSet
.insert(varDecl
);
501 bool ConstVars::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
502 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
504 unsigned len
= std::min(callExpr
.getNumArgs(), calleeFunctionDecl
.getNumParams());
505 // if it's an array, passing it by value to a method typically means the
506 // callee takes a pointer and can modify the array
507 if (varDecl
->getType()->isConstantArrayType())
509 for (unsigned i
= 0; i
< len
; ++i
)
510 if (callExpr
.getArg(i
) == child
)
511 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
516 for (unsigned i
= 0; i
< len
; ++i
)
517 if (callExpr
.getArg(i
) == child
)
519 auto tc
= loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
));
520 if (tc
.LvalueReference().NonConst() || tc
.Pointer().NonConst())
527 llvm::Optional
<CalleeWrapper
> ConstVars::getCallee(CallExpr
const* callExpr
)
529 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
531 return CalleeWrapper(functionDecl
);
533 // Extract the functionprototype from a type
534 clang::Type
const* calleeType
= callExpr
->getCallee()->getType().getTypePtr();
535 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>())
537 if (auto prototype
= pointerType
->getPointeeType()
538 ->getUnqualifiedDesugaredType()
539 ->getAs
<FunctionProtoType
>())
541 return CalleeWrapper(prototype
);
545 return llvm::Optional
<CalleeWrapper
>();
548 /** off by default because it is very expensive, it walks up the AST a lot */
549 loplugin::Plugin::Registration
<ConstVars
> X("constvars", false);
554 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */