bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / constvars.cxx
blobd4a431dc14f0df64ee949cd65c7364ec64d5fce5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <unordered_set>
17 #include <vector>
18 #include <algorithm>
19 #include <sys/file.h>
20 #include <unistd.h>
21 #include "plugin.hxx"
22 #include "compat.hxx"
23 #include "check.hxx"
25 /**
26 Look for static vars that are only assigned to once, and never written to, they can be const.
29 namespace
31 /**
32 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
34 class CallerWrapper
36 const CallExpr* m_callExpr;
37 const CXXConstructExpr* m_cxxConstructExpr;
39 public:
40 CallerWrapper(const CallExpr* callExpr)
41 : m_callExpr(callExpr)
42 , m_cxxConstructExpr(nullptr)
45 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
46 : m_callExpr(nullptr)
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);
59 class CalleeWrapper
61 const FunctionDecl* m_calleeFunctionDecl = nullptr;
62 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
63 const FunctionProtoType* m_functionPrototype = nullptr;
65 public:
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
86 return 0;
87 else
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();
96 else
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();
105 else
106 return "";
108 CXXMethodDecl const* getAsCXXMethodDecl() const
110 if (m_calleeFunctionDecl)
111 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
112 return nullptr;
116 class ConstVars : public RecursiveASTVisitor<ConstVars>, public loplugin::Plugin
118 public:
119 explicit ConstVars(loplugin::InstantiationData const& data)
120 : Plugin(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*);
137 private:
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)
158 return;
160 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
162 SourceManager& SM = compiler.getSourceManager();
163 for (VarDecl const* v : definitionSet)
165 if (cannotBeConstSet.find(v) != cannotBeConstSet.end())
166 continue;
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"))
170 continue;
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))
179 return true;
180 if (!varDecl->hasGlobalStorage())
181 return true;
182 if (isa<ParmVarDecl>(varDecl))
183 return true;
184 if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage)
185 return true;
186 if (varDecl->getType().isConstQualified())
187 return true;
188 if (isa<ConstantArrayType>(varDecl->getType()))
189 return true;
190 if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const())
191 return true;
192 // ignore stuff that forms part of the stable URE interface
193 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
194 return true;
196 if (!varDecl->getInit())
197 return true;
198 if (varDecl->getInit()->isInstantiationDependent())
199 return true;
200 if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext()))
201 return true;
203 definitionSet.insert(varDecl);
204 return true;
207 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt)
209 if (compat::getBeginLoc(forStmt).isValid() && ignoreLocation(forStmt))
210 return true;
211 const VarDecl* varDecl = forStmt->getLoopVariable();
212 if (!varDecl)
213 return true;
214 // we don't handle structured assignment properly
215 if (isa<DecompositionDecl>(varDecl))
216 return true;
217 auto tc = loplugin::TypeCheck(varDecl->getType());
218 if (!tc.LvalueReference())
219 return true;
220 if (tc.LvalueReference().Const())
221 return true;
223 definitionSet.insert(varDecl);
224 return true;
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;
237 return ret;
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;
253 return ret;
256 bool ConstVars::TraverseFunctionDecl(FunctionDecl* functionDecl)
258 auto copy2 = insideFunctionDecl;
259 insideFunctionDecl = functionDecl;
260 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
261 insideFunctionDecl = copy2;
262 return ret;
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);
275 if (varDecl)
276 insideConditionalCheckOfVarSet.pop_back();
277 return ret;
280 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
282 const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
283 if (!varDecl)
284 return true;
285 varDecl = varDecl->getCanonicalDecl();
286 if (ignoreLocation(varDecl))
287 return true;
288 // ignore stuff that forms part of the stable URE interface
289 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
290 return true;
292 if (definitionSet.find(varDecl) != definitionSet.end())
293 check(varDecl, declRefExpr);
295 return true;
298 void ConstVars::check(const VarDecl* varDecl, const Expr* memberExpr)
300 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
301 const Stmt* child = memberExpr;
302 const Stmt* parent
303 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
305 // walk up the tree until we find something interesting
307 bool bCannotBeConst = false;
308 bool bDump = false;
309 auto walkUp = [&]() {
310 child = parent;
311 auto parentsRange = compiler.getASTContext().getParents(*parent);
312 parent = parentsRange.begin() == parentsRange.end() ? nullptr
313 : parentsRange.begin()->get<Stmt>();
317 if (!parent)
319 // check if we have an expression like
320 // int& r = var;
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>());
325 if (varDecl)
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;
337 break;
342 if (!parent)
343 break;
344 if (isa<CXXReinterpretCastExpr>(parent))
346 // once we see one of these, there is not much useful we can know
347 bCannotBeConst = true;
348 break;
350 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
351 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
352 || isa<ExprWithCleanups>(parent))
354 walkUp();
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
360 || op == UO_PreDec)
362 bCannotBeConst = true;
364 walkUp();
366 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
368 auto callee = getCallee(operatorCallExpr);
369 if (callee)
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;
383 else
384 bCannotBeConst = true; // conservative, could improve
385 walkUp();
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;
402 break;
404 if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
405 CalleeWrapper(calleeMethodDecl)))
406 bCannotBeConst = true;
408 else
409 bCannotBeConst = true; // can happen in templates
410 walkUp();
412 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
414 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
415 CalleeWrapper(cxxConstructExpr)))
416 bCannotBeConst = true;
417 walkUp();
419 else if (auto callExpr = dyn_cast<CallExpr>(parent))
421 auto callee = getCallee(callExpr);
422 if (callee)
424 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
425 bCannotBeConst = true;
427 else
428 bCannotBeConst = true; // conservative, could improve
429 walkUp();
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;
439 if (assignmentOp)
441 if (binaryOp->getLHS() == child)
442 bCannotBeConst = true;
443 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
444 .LvalueReference()
445 .NonConst())
446 // if the LHS is a non-const reference, we could write to the var later on
447 bCannotBeConst = true;
449 walkUp();
451 else if (isa<ReturnStmt>(parent))
453 if (insideFunctionDecl)
455 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
456 if (tc.LvalueReference().NonConst())
457 bCannotBeConst = true;
459 break;
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;
469 break;
471 else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
472 || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<DefaultStmt>(parent))
474 break;
476 else
478 walkUp();
480 } while (true);
482 if (bDump)
484 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
485 compat::getBeginLoc(memberExpr))
486 << bCannotBeConst << memberExpr->getSourceRange();
487 if (parent)
489 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
490 << parent->getSourceRange();
491 parent->dump();
493 memberExpr->dump();
494 varDecl->getType()->dump();
497 if (bCannotBeConst)
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())
512 return true;
514 else
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())
521 return true;
524 return false;
527 llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr)
529 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
530 if (functionDecl)
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);
552 #endif
554 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */