nss: upgrade to release 3.73
[LibreOffice.git] / compilerplugins / clang / constvars.cxx
blobf89301fed205894c3108a1c4c0e6aafd183dd2ab
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>
22 #include "config_clang.h"
24 #include "plugin.hxx"
25 #include "compat.hxx"
26 #include "check.hxx"
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
30 #endif
32 /**
33 Look for static vars that are only assigned to once, and never written to, they can be const.
36 namespace
38 /**
39 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
41 class CallerWrapper
43 const CallExpr* m_callExpr;
44 const CXXConstructExpr* m_cxxConstructExpr;
46 public:
47 CallerWrapper(const CallExpr* callExpr)
48 : m_callExpr(callExpr)
49 , m_cxxConstructExpr(nullptr)
52 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
53 : m_callExpr(nullptr)
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);
66 class CalleeWrapper
68 const FunctionDecl* m_calleeFunctionDecl = nullptr;
69 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
70 const FunctionProtoType* m_functionPrototype = nullptr;
72 public:
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
93 return 0;
94 else
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();
103 else
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();
112 else
113 return "";
115 CXXMethodDecl const* getAsCXXMethodDecl() const
117 if (m_calleeFunctionDecl)
118 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
119 return nullptr;
123 class ConstVars : public RecursiveASTVisitor<ConstVars>, public loplugin::Plugin
125 public:
126 explicit ConstVars(loplugin::InstantiationData const& data)
127 : Plugin(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*);
144 private:
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)
165 return;
167 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
169 SourceManager& SM = compiler.getSourceManager();
170 for (VarDecl const* v : definitionSet)
172 if (cannotBeConstSet.find(v) != cannotBeConstSet.end())
173 continue;
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"))
177 continue;
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))
186 return true;
187 if (!varDecl->hasGlobalStorage())
188 return true;
189 if (isa<ParmVarDecl>(varDecl))
190 return true;
191 if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage)
192 return true;
193 if (varDecl->getType().isConstQualified())
194 return true;
195 if (isa<ConstantArrayType>(varDecl->getType()))
196 return true;
197 if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const())
198 return true;
199 // ignore stuff that forms part of the stable URE interface
200 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
201 return true;
203 if (!varDecl->getInit())
204 return true;
205 if (varDecl->getInit()->isInstantiationDependent())
206 return true;
207 if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext()))
208 return true;
210 definitionSet.insert(varDecl);
211 return true;
214 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt)
216 if (compat::getBeginLoc(forStmt).isValid() && ignoreLocation(forStmt))
217 return true;
218 const VarDecl* varDecl = forStmt->getLoopVariable();
219 if (!varDecl)
220 return true;
221 // we don't handle structured assignment properly
222 if (isa<DecompositionDecl>(varDecl))
223 return true;
224 auto tc = loplugin::TypeCheck(varDecl->getType());
225 if (!tc.LvalueReference())
226 return true;
227 if (tc.LvalueReference().Const())
228 return true;
230 definitionSet.insert(varDecl);
231 return true;
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;
244 return ret;
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;
260 return ret;
263 bool ConstVars::TraverseFunctionDecl(FunctionDecl* functionDecl)
265 auto copy2 = insideFunctionDecl;
266 insideFunctionDecl = functionDecl;
267 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
268 insideFunctionDecl = copy2;
269 return ret;
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);
282 if (varDecl)
283 insideConditionalCheckOfVarSet.pop_back();
284 return ret;
287 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
289 const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
290 if (!varDecl)
291 return true;
292 varDecl = varDecl->getCanonicalDecl();
293 if (ignoreLocation(varDecl))
294 return true;
295 // ignore stuff that forms part of the stable URE interface
296 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
297 return true;
299 if (definitionSet.find(varDecl) != definitionSet.end())
300 check(varDecl, declRefExpr);
302 return true;
305 void ConstVars::check(const VarDecl* varDecl, const Expr* memberExpr)
307 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
308 const Stmt* child = memberExpr;
309 const Stmt* parent
310 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
312 // walk up the tree until we find something interesting
314 bool bCannotBeConst = false;
315 bool bDump = false;
316 auto walkUp = [&]() {
317 child = parent;
318 auto parentsRange = compiler.getASTContext().getParents(*parent);
319 parent = parentsRange.begin() == parentsRange.end() ? nullptr
320 : parentsRange.begin()->get<Stmt>();
324 if (!parent)
326 // check if we have an expression like
327 // int& r = var;
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>());
332 if (varDecl)
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;
344 break;
349 if (!parent)
350 break;
351 if (isa<CXXReinterpretCastExpr>(parent))
353 // once we see one of these, there is not much useful we can know
354 bCannotBeConst = true;
355 break;
357 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
358 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
359 || isa<ExprWithCleanups>(parent))
361 walkUp();
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
367 || op == UO_PreDec)
369 bCannotBeConst = true;
371 walkUp();
373 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
375 auto callee = getCallee(operatorCallExpr);
376 if (callee)
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;
390 else
391 bCannotBeConst = true; // conservative, could improve
392 walkUp();
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;
409 break;
411 if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
412 CalleeWrapper(calleeMethodDecl)))
413 bCannotBeConst = true;
415 else
416 bCannotBeConst = true; // can happen in templates
417 walkUp();
419 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
421 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
422 CalleeWrapper(cxxConstructExpr)))
423 bCannotBeConst = true;
424 walkUp();
426 else if (auto callExpr = dyn_cast<CallExpr>(parent))
428 auto callee = getCallee(callExpr);
429 if (callee)
431 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
432 bCannotBeConst = true;
434 else
435 bCannotBeConst = true; // conservative, could improve
436 walkUp();
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;
446 if (assignmentOp)
448 if (binaryOp->getLHS() == child)
449 bCannotBeConst = true;
450 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
451 .LvalueReference()
452 .NonConst())
453 // if the LHS is a non-const reference, we could write to the var later on
454 bCannotBeConst = true;
456 walkUp();
458 else if (isa<ReturnStmt>(parent))
460 if (insideFunctionDecl)
462 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
463 if (tc.LvalueReference().NonConst())
464 bCannotBeConst = true;
466 break;
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;
476 break;
478 else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
479 || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<DefaultStmt>(parent))
481 break;
483 else
485 walkUp();
487 } while (true);
489 if (bDump)
491 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
492 compat::getBeginLoc(memberExpr))
493 << bCannotBeConst << memberExpr->getSourceRange();
494 if (parent)
496 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
497 << parent->getSourceRange();
498 parent->dump();
500 memberExpr->dump();
501 varDecl->getType()->dump();
504 if (bCannotBeConst)
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())
519 return true;
521 else
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())
528 return true;
531 return false;
534 llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr)
536 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
537 if (functionDecl)
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);
559 #endif
561 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */