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 Look for static vars that are only assigned to once, and never written to, they can be const.
39 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
43 const CallExpr
* m_callExpr
;
44 const CXXConstructExpr
* m_cxxConstructExpr
;
47 CallerWrapper(const CallExpr
* callExpr
)
48 : m_callExpr(callExpr
)
49 , m_cxxConstructExpr(nullptr)
52 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
54 , m_cxxConstructExpr(cxxConstructExpr
)
57 unsigned getNumArgs() const
59 return m_callExpr
? m_callExpr
->getNumArgs() : m_cxxConstructExpr
->getNumArgs();
61 const Expr
* getArg(unsigned i
) const
63 return m_callExpr
? m_callExpr
->getArg(i
) : m_cxxConstructExpr
->getArg(i
);
68 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
69 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
70 const FunctionProtoType
* m_functionPrototype
= nullptr;
73 explicit CalleeWrapper(const FunctionDecl
* calleeFunctionDecl
)
74 : m_calleeFunctionDecl(calleeFunctionDecl
)
77 explicit CalleeWrapper(const CXXConstructExpr
* cxxConstructExpr
)
78 : m_cxxConstructorDecl(cxxConstructExpr
->getConstructor())
81 explicit CalleeWrapper(const FunctionProtoType
* functionPrototype
)
82 : m_functionPrototype(functionPrototype
)
85 unsigned getNumParams() const
87 if (m_calleeFunctionDecl
)
88 return m_calleeFunctionDecl
->getNumParams();
89 else if (m_cxxConstructorDecl
)
90 return m_cxxConstructorDecl
->getNumParams();
91 else if (m_functionPrototype
->param_type_begin() == m_functionPrototype
->param_type_end())
92 // FunctionProtoType will assert if we call getParamTypes() and it has no params
95 return m_functionPrototype
->getParamTypes().size();
97 const QualType
getParamType(unsigned i
) const
99 if (m_calleeFunctionDecl
)
100 return m_calleeFunctionDecl
->getParamDecl(i
)->getType();
101 else if (m_cxxConstructorDecl
)
102 return m_cxxConstructorDecl
->getParamDecl(i
)->getType();
104 return m_functionPrototype
->getParamTypes()[i
];
106 std::string
getNameAsString() const
108 if (m_calleeFunctionDecl
)
109 return m_calleeFunctionDecl
->getNameAsString();
110 else if (m_cxxConstructorDecl
)
111 return m_cxxConstructorDecl
->getNameAsString();
115 CXXMethodDecl
const* getAsCXXMethodDecl() const
117 if (m_calleeFunctionDecl
)
118 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
123 class ConstVars
: public RecursiveASTVisitor
<ConstVars
>, public loplugin::Plugin
126 explicit ConstVars(loplugin::InstantiationData
const& data
)
131 virtual void run() override
;
133 bool shouldVisitTemplateInstantiations() const { return true; }
134 bool shouldVisitImplicitCode() const { return true; }
136 bool VisitVarDecl(const VarDecl
*);
137 bool VisitCXXForRangeStmt(const CXXForRangeStmt
*);
138 bool VisitDeclRefExpr(const DeclRefExpr
*);
139 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
140 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
141 bool TraverseFunctionDecl(FunctionDecl
*);
142 bool TraverseIfStmt(IfStmt
*);
145 void check(const VarDecl
* varDecl
, const Expr
* memberExpr
);
146 bool isSomeKindOfZero(const Expr
* arg
);
147 bool IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
, CallerWrapper callExpr
,
148 CalleeWrapper calleeFunctionDecl
);
149 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const*);
151 RecordDecl
* insideMoveOrCopyDeclParent
= nullptr;
152 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
153 // we store the parent function on the way down the AST.
154 FunctionDecl
* insideFunctionDecl
= nullptr;
155 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
157 std::set
<VarDecl
const*> cannotBeConstSet
;
158 std::set
<VarDecl
const*> definitionSet
;
161 void ConstVars::run()
163 // clang::Expr::isCXX11ConstantExpr only works for C++
164 if (!compiler
.getLangOpts().CPlusPlus
)
167 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
169 SourceManager
& SM
= compiler
.getSourceManager();
170 for (VarDecl
const* v
: definitionSet
)
172 if (cannotBeConstSet
.find(v
) != cannotBeConstSet
.end())
174 llvm::StringRef
sourceString(SM
.getCharacterData(v
->getSourceRange().getEnd()), 50);
175 // Implement a marker that disables this plugins warning at a specific site
176 if (sourceString
.contains("loplugin:constvars:ignore"))
178 report(DiagnosticsEngine::Warning
, "var can be const", compat::getBeginLoc(v
));
182 bool ConstVars::VisitVarDecl(const VarDecl
* varDecl
)
184 varDecl
= varDecl
->getCanonicalDecl();
185 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
187 if (!varDecl
->hasGlobalStorage())
189 if (isa
<ParmVarDecl
>(varDecl
))
191 if (varDecl
->getLinkageAndVisibility().getLinkage() == ExternalLinkage
)
193 if (varDecl
->getType().isConstQualified())
195 if (isa
<ConstantArrayType
>(varDecl
->getType()))
197 if (loplugin::TypeCheck(varDecl
->getType()).Pointer().Const())
199 // ignore stuff that forms part of the stable URE interface
200 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
203 if (!varDecl
->getInit())
205 if (varDecl
->getInit()->isInstantiationDependent())
207 if (!varDecl
->getInit()->isCXX11ConstantExpr(compiler
.getASTContext()))
210 definitionSet
.insert(varDecl
);
214 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt
* forStmt
)
216 if (compat::getBeginLoc(forStmt
).isValid() && ignoreLocation(forStmt
))
218 const VarDecl
* varDecl
= forStmt
->getLoopVariable();
221 // we don't handle structured assignment properly
222 if (isa
<DecompositionDecl
>(varDecl
))
224 auto tc
= loplugin::TypeCheck(varDecl
->getType());
225 if (!tc
.LvalueReference())
227 if (tc
.LvalueReference().Const())
230 definitionSet
.insert(varDecl
);
234 bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
236 auto copy
= insideMoveOrCopyDeclParent
;
237 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
239 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
240 insideMoveOrCopyDeclParent
= cxxConstructorDecl
->getParent();
242 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
243 insideMoveOrCopyDeclParent
= copy
;
247 bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
249 auto copy1
= insideMoveOrCopyDeclParent
;
250 auto copy2
= insideFunctionDecl
;
251 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
253 if (cxxMethodDecl
->isCopyAssignmentOperator() || cxxMethodDecl
->isMoveAssignmentOperator())
254 insideMoveOrCopyDeclParent
= cxxMethodDecl
->getParent();
256 insideFunctionDecl
= cxxMethodDecl
;
257 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
258 insideMoveOrCopyDeclParent
= copy1
;
259 insideFunctionDecl
= copy2
;
263 bool ConstVars::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
265 auto copy2
= insideFunctionDecl
;
266 insideFunctionDecl
= functionDecl
;
267 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
268 insideFunctionDecl
= copy2
;
272 bool ConstVars::TraverseIfStmt(IfStmt
* ifStmt
)
274 VarDecl
const* varDecl
= nullptr;
275 Expr
const* cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
276 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cond
))
278 if ((varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl())))
279 insideConditionalCheckOfVarSet
.push_back(varDecl
);
281 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
283 insideConditionalCheckOfVarSet
.pop_back();
287 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
289 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl());
292 varDecl
= varDecl
->getCanonicalDecl();
293 if (ignoreLocation(varDecl
))
295 // ignore stuff that forms part of the stable URE interface
296 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
299 if (definitionSet
.find(varDecl
) != definitionSet
.end())
300 check(varDecl
, declRefExpr
);
305 void ConstVars::check(const VarDecl
* varDecl
, const Expr
* memberExpr
)
307 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
308 const Stmt
* child
= memberExpr
;
310 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
312 // walk up the tree until we find something interesting
314 bool bCannotBeConst
= false;
316 auto walkUp
= [&]() {
318 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
319 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
320 : parentsRange
.begin()->get
<Stmt
>();
326 // check if we have an expression like
328 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
329 if (parentsRange
.begin() != parentsRange
.end())
331 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
334 if (varDecl
->isImplicit())
336 // so we can walk up from inside a for-range stmt
337 parentsRange
= compiler
.getASTContext().getParents(*varDecl
);
338 if (parentsRange
.begin() != parentsRange
.end())
339 parent
= parentsRange
.begin()->get
<Stmt
>();
341 else if (loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
343 bCannotBeConst
= true;
351 if (isa
<CXXReinterpretCastExpr
>(parent
))
353 // once we see one of these, there is not much useful we can know
354 bCannotBeConst
= true;
357 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
358 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
359 || isa
<ExprWithCleanups
>(parent
))
363 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
365 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
366 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
369 bCannotBeConst
= true;
373 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
375 auto callee
= getCallee(operatorCallExpr
);
378 // if calling a non-const operator on the var
379 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
380 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
381 && !calleeMethodDecl
->isConst())
383 bCannotBeConst
= true;
385 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
387 bCannotBeConst
= true;
391 bCannotBeConst
= true; // conservative, could improve
394 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
396 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
397 if (calleeMethodDecl
)
399 // if calling a non-const method on the var
400 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
401 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
403 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
405 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
406 && !calleeMethodDecl
->isConst())
408 bCannotBeConst
= true;
411 if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
412 CalleeWrapper(calleeMethodDecl
)))
413 bCannotBeConst
= true;
416 bCannotBeConst
= true; // can happen in templates
419 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
421 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
422 CalleeWrapper(cxxConstructExpr
)))
423 bCannotBeConst
= true;
426 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
428 auto callee
= getCallee(callExpr
);
431 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
432 bCannotBeConst
= true;
435 bCannotBeConst
= true; // conservative, could improve
438 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
440 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
441 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
442 || op
== BO_RemAssign
|| op
== BO_AddAssign
443 || op
== BO_SubAssign
|| op
== BO_ShlAssign
444 || op
== BO_ShrAssign
|| op
== BO_AndAssign
445 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
448 if (binaryOp
->getLHS() == child
)
449 bCannotBeConst
= true;
450 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
453 // if the LHS is a non-const reference, we could write to the var later on
454 bCannotBeConst
= true;
458 else if (isa
<ReturnStmt
>(parent
))
460 if (insideFunctionDecl
)
462 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
463 if (tc
.LvalueReference().NonConst())
464 bCannotBeConst
= true;
468 else if (auto rangeStmt
= dyn_cast
<CXXForRangeStmt
>(parent
))
470 if (rangeStmt
->getRangeStmt() == child
)
472 auto tc
= loplugin::TypeCheck(rangeStmt
->getLoopVariable()->getType());
473 if (tc
.LvalueReference().NonConst())
474 bCannotBeConst
= true;
478 else if (isa
<SwitchStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<ForStmt
>(parent
)
479 || isa
<IfStmt
>(parent
) || isa
<DoStmt
>(parent
) || isa
<DefaultStmt
>(parent
))
491 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
492 compat::getBeginLoc(memberExpr
))
493 << bCannotBeConst
<< memberExpr
->getSourceRange();
496 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
497 << parent
->getSourceRange();
501 varDecl
->getType()->dump();
505 cannotBeConstSet
.insert(varDecl
);
508 bool ConstVars::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
509 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
511 unsigned len
= std::min(callExpr
.getNumArgs(), calleeFunctionDecl
.getNumParams());
512 // if it's an array, passing it by value to a method typically means the
513 // callee takes a pointer and can modify the array
514 if (varDecl
->getType()->isConstantArrayType())
516 for (unsigned i
= 0; i
< len
; ++i
)
517 if (callExpr
.getArg(i
) == child
)
518 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
523 for (unsigned i
= 0; i
< len
; ++i
)
524 if (callExpr
.getArg(i
) == child
)
526 auto tc
= loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
));
527 if (tc
.LvalueReference().NonConst() || tc
.Pointer().NonConst())
534 llvm::Optional
<CalleeWrapper
> ConstVars::getCallee(CallExpr
const* callExpr
)
536 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
538 return CalleeWrapper(functionDecl
);
540 // Extract the functionprototype from a type
541 clang::Type
const* calleeType
= callExpr
->getCallee()->getType().getTypePtr();
542 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>())
544 if (auto prototype
= pointerType
->getPointeeType()
545 ->getUnqualifiedDesugaredType()
546 ->getAs
<FunctionProtoType
>())
548 return CalleeWrapper(prototype
);
552 return llvm::Optional
<CalleeWrapper
>();
555 /** off by default because it is very expensive, it walks up the AST a lot */
556 loplugin::Plugin::Registration
<ConstVars
> X("constvars", false);
561 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */