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 VisitDeclRefExpr(const DeclRefExpr
*);
131 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
132 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
133 bool TraverseFunctionDecl(FunctionDecl
*);
134 bool TraverseIfStmt(IfStmt
*);
137 void check(const VarDecl
* varDecl
, const Expr
* memberExpr
);
138 bool isSomeKindOfZero(const Expr
* arg
);
139 bool IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
, CallerWrapper callExpr
,
140 CalleeWrapper calleeFunctionDecl
);
141 llvm::Optional
<CalleeWrapper
> getCallee(CallExpr
const*);
143 RecordDecl
* insideMoveOrCopyDeclParent
= nullptr;
144 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
145 // we store the parent function on the way down the AST.
146 FunctionDecl
* insideFunctionDecl
= nullptr;
147 std::vector
<VarDecl
const*> insideConditionalCheckOfVarSet
;
149 std::set
<VarDecl
const*> cannotBeConstSet
;
150 std::set
<VarDecl
const*> definitionSet
;
153 void ConstVars::run()
155 // clang::Expr::isCXX11ConstantExpr only works for C++
156 if (!compiler
.getLangOpts().CPlusPlus
)
159 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
161 SourceManager
& SM
= compiler
.getSourceManager();
162 for (VarDecl
const* v
: definitionSet
)
164 if (cannotBeConstSet
.find(v
) != cannotBeConstSet
.end())
166 llvm::StringRef
sourceString(SM
.getCharacterData(v
->getSourceRange().getEnd()), 50);
167 // Implement a marker that disables this plugins warning at a specific site
168 if (sourceString
.contains("loplugin:constvars:ignore"))
170 report(DiagnosticsEngine::Warning
, "static var can be const", compat::getBeginLoc(v
));
174 bool ConstVars::VisitVarDecl(const VarDecl
* varDecl
)
176 varDecl
= varDecl
->getCanonicalDecl();
177 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
179 if (!varDecl
->hasGlobalStorage())
181 if (varDecl
->getLinkageAndVisibility().getLinkage() == ExternalLinkage
)
183 if (varDecl
->getType().isConstQualified())
185 if (isa
<ConstantArrayType
>(varDecl
->getType()))
187 if (loplugin::TypeCheck(varDecl
->getType()).Pointer().Const())
189 // ignore stuff that forms part of the stable URE interface
190 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
193 if (!varDecl
->getInit())
195 if (!varDecl
->getInit()->isCXX11ConstantExpr(compiler
.getASTContext()))
198 definitionSet
.insert(varDecl
);
202 bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
204 auto copy
= insideMoveOrCopyDeclParent
;
205 if (!ignoreLocation(cxxConstructorDecl
) && cxxConstructorDecl
->isThisDeclarationADefinition())
207 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
208 insideMoveOrCopyDeclParent
= cxxConstructorDecl
->getParent();
210 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
211 insideMoveOrCopyDeclParent
= copy
;
215 bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
217 auto copy1
= insideMoveOrCopyDeclParent
;
218 auto copy2
= insideFunctionDecl
;
219 if (!ignoreLocation(cxxMethodDecl
) && cxxMethodDecl
->isThisDeclarationADefinition())
221 if (cxxMethodDecl
->isCopyAssignmentOperator() || cxxMethodDecl
->isMoveAssignmentOperator())
222 insideMoveOrCopyDeclParent
= cxxMethodDecl
->getParent();
224 insideFunctionDecl
= cxxMethodDecl
;
225 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
226 insideMoveOrCopyDeclParent
= copy1
;
227 insideFunctionDecl
= copy2
;
231 bool ConstVars::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
233 auto copy2
= insideFunctionDecl
;
234 insideFunctionDecl
= functionDecl
;
235 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
236 insideFunctionDecl
= copy2
;
240 bool ConstVars::TraverseIfStmt(IfStmt
* ifStmt
)
242 VarDecl
const* varDecl
= nullptr;
243 Expr
const* cond
= ifStmt
->getCond()->IgnoreParenImpCasts();
244 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cond
))
246 if ((varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl())))
247 insideConditionalCheckOfVarSet
.push_back(varDecl
);
249 bool ret
= RecursiveASTVisitor::TraverseIfStmt(ifStmt
);
251 insideConditionalCheckOfVarSet
.pop_back();
255 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
257 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl());
260 varDecl
= varDecl
->getCanonicalDecl();
261 if (ignoreLocation(varDecl
))
263 // ignore stuff that forms part of the stable URE interface
264 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(varDecl
->getLocation())))
267 if (definitionSet
.find(varDecl
) != definitionSet
.end())
268 check(varDecl
, declRefExpr
);
273 void ConstVars::check(const VarDecl
* varDecl
, const Expr
* memberExpr
)
275 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
276 const Stmt
* child
= memberExpr
;
278 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
279 // walk up the tree until we find something interesting
280 bool bCannotBeConst
= false;
282 auto walkUp
= [&]() {
284 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
285 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
286 : parentsRange
.begin()->get
<Stmt
>();
292 // check if we have an expression like
294 auto parentsRange
= compiler
.getASTContext().getParents(*child
);
295 if (parentsRange
.begin() != parentsRange
.end())
297 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
298 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
299 // which is of type 'T&&' and also an l-value-ref ?
300 if (varDecl
&& !varDecl
->isImplicit()
301 && loplugin::TypeCheck(varDecl
->getType()).LvalueReference().NonConst())
303 bCannotBeConst
= true;
308 if (isa
<CXXReinterpretCastExpr
>(parent
))
310 // once we see one of these, there is not much useful we can know
311 bCannotBeConst
= true;
314 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
315 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
316 || isa
<ExprWithCleanups
>(parent
))
320 else if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
))
322 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
323 if (op
== UO_AddrOf
|| op
== UO_PostInc
|| op
== UO_PostDec
|| op
== UO_PreInc
326 bCannotBeConst
= true;
330 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
332 auto callee
= getCallee(operatorCallExpr
);
335 // if calling a non-const operator on the var
336 auto calleeMethodDecl
= callee
->getAsCXXMethodDecl();
337 if (calleeMethodDecl
&& operatorCallExpr
->getArg(0) == child
338 && !calleeMethodDecl
->isConst())
340 bCannotBeConst
= true;
342 else if (IsPassedByNonConst(varDecl
, child
, operatorCallExpr
, *callee
))
344 bCannotBeConst
= true;
348 bCannotBeConst
= true; // conservative, could improve
351 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
353 const CXXMethodDecl
* calleeMethodDecl
= cxxMemberCallExpr
->getMethodDecl();
354 if (calleeMethodDecl
)
356 // if calling a non-const method on the var
357 const Expr
* tmp
= dyn_cast
<Expr
>(child
);
358 if (tmp
->isBoundMemberFunction(compiler
.getASTContext()))
360 tmp
= dyn_cast
<MemberExpr
>(tmp
)->getBase();
362 if (cxxMemberCallExpr
->getImplicitObjectArgument() == tmp
363 && !calleeMethodDecl
->isConst())
365 bCannotBeConst
= true;
368 if (IsPassedByNonConst(varDecl
, child
, cxxMemberCallExpr
,
369 CalleeWrapper(calleeMethodDecl
)))
370 bCannotBeConst
= true;
373 bCannotBeConst
= true; // can happen in templates
376 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
378 if (IsPassedByNonConst(varDecl
, child
, cxxConstructExpr
,
379 CalleeWrapper(cxxConstructExpr
)))
380 bCannotBeConst
= true;
383 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
385 auto callee
= getCallee(callExpr
);
388 if (IsPassedByNonConst(varDecl
, child
, callExpr
, *callee
))
389 bCannotBeConst
= true;
392 bCannotBeConst
= true; // conservative, could improve
395 else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
397 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
398 const bool assignmentOp
= op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
399 || op
== BO_RemAssign
|| op
== BO_AddAssign
400 || op
== BO_SubAssign
|| op
== BO_ShlAssign
401 || op
== BO_ShrAssign
|| op
== BO_AndAssign
402 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
405 if (binaryOp
->getLHS() == child
)
406 bCannotBeConst
= true;
407 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
410 // if the LHS is a non-const reference, we could write to the var later on
411 bCannotBeConst
= true;
415 else if (isa
<ReturnStmt
>(parent
))
417 if (insideFunctionDecl
)
419 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
420 if (tc
.LvalueReference().NonConst())
421 bCannotBeConst
= true;
425 else if (isa
<SwitchStmt
>(parent
) || isa
<WhileStmt
>(parent
) || isa
<ForStmt
>(parent
)
426 || isa
<IfStmt
>(parent
) || isa
<DoStmt
>(parent
) || isa
<CXXForRangeStmt
>(parent
)
427 || isa
<DefaultStmt
>(parent
))
439 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
440 compat::getBeginLoc(memberExpr
))
441 << bCannotBeConst
<< memberExpr
->getSourceRange();
444 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
445 << parent
->getSourceRange();
449 varDecl
->getType()->dump();
453 cannotBeConstSet
.insert(varDecl
);
456 bool ConstVars::IsPassedByNonConst(const VarDecl
* varDecl
, const Stmt
* child
,
457 CallerWrapper callExpr
, CalleeWrapper calleeFunctionDecl
)
459 unsigned len
= std::min(callExpr
.getNumArgs(), calleeFunctionDecl
.getNumParams());
460 // if it's an array, passing it by value to a method typically means the
461 // callee takes a pointer and can modify the array
462 if (varDecl
->getType()->isConstantArrayType())
464 for (unsigned i
= 0; i
< len
; ++i
)
465 if (callExpr
.getArg(i
) == child
)
466 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).Pointer().NonConst())
471 for (unsigned i
= 0; i
< len
; ++i
)
472 if (callExpr
.getArg(i
) == child
)
473 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
481 llvm::Optional
<CalleeWrapper
> ConstVars::getCallee(CallExpr
const* callExpr
)
483 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
485 return CalleeWrapper(functionDecl
);
487 // Extract the functionprototype from a type
488 clang::Type
const* calleeType
= callExpr
->getCallee()->getType().getTypePtr();
489 if (auto pointerType
= calleeType
->getUnqualifiedDesugaredType()->getAs
<clang::PointerType
>())
491 if (auto prototype
= pointerType
->getPointeeType()
492 ->getUnqualifiedDesugaredType()
493 ->getAs
<FunctionProtoType
>())
495 return CalleeWrapper(prototype
);
499 return llvm::Optional
<CalleeWrapper
>();
502 /** off by default because it is very expensive, it walks up the AST a lot */
503 loplugin::Plugin::Registration
<ConstVars
> X("constvars", false);
508 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */