bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / constvars.cxx
blob56f863407f0bd6aa13985e2eb4dd468a6612cfb1
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 VisitDeclRefExpr(const DeclRefExpr*);
131 bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
132 bool TraverseCXXMethodDecl(CXXMethodDecl*);
133 bool TraverseFunctionDecl(FunctionDecl*);
134 bool TraverseIfStmt(IfStmt*);
136 private:
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)
157 return;
159 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
161 SourceManager& SM = compiler.getSourceManager();
162 for (VarDecl const* v : definitionSet)
164 if (cannotBeConstSet.find(v) != cannotBeConstSet.end())
165 continue;
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"))
169 continue;
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))
178 return true;
179 if (!varDecl->hasGlobalStorage())
180 return true;
181 if (varDecl->getLinkageAndVisibility().getLinkage() == ExternalLinkage)
182 return true;
183 if (varDecl->getType().isConstQualified())
184 return true;
185 if (isa<ConstantArrayType>(varDecl->getType()))
186 return true;
187 if (loplugin::TypeCheck(varDecl->getType()).Pointer().Const())
188 return true;
189 // ignore stuff that forms part of the stable URE interface
190 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
191 return true;
193 if (!varDecl->getInit())
194 return true;
195 if (!varDecl->getInit()->isCXX11ConstantExpr(compiler.getASTContext()))
196 return true;
198 definitionSet.insert(varDecl);
199 return true;
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;
212 return ret;
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;
228 return ret;
231 bool ConstVars::TraverseFunctionDecl(FunctionDecl* functionDecl)
233 auto copy2 = insideFunctionDecl;
234 insideFunctionDecl = functionDecl;
235 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
236 insideFunctionDecl = copy2;
237 return ret;
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);
250 if (varDecl)
251 insideConditionalCheckOfVarSet.pop_back();
252 return ret;
255 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
257 const VarDecl* varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
258 if (!varDecl)
259 return true;
260 varDecl = varDecl->getCanonicalDecl();
261 if (ignoreLocation(varDecl))
262 return true;
263 // ignore stuff that forms part of the stable URE interface
264 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
265 return true;
267 if (definitionSet.find(varDecl) != definitionSet.end())
268 check(varDecl, declRefExpr);
270 return true;
273 void ConstVars::check(const VarDecl* varDecl, const Expr* memberExpr)
275 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
276 const Stmt* child = memberExpr;
277 const Stmt* parent
278 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
279 // walk up the tree until we find something interesting
280 bool bCannotBeConst = false;
281 bool bDump = false;
282 auto walkUp = [&]() {
283 child = parent;
284 auto parentsRange = compiler.getASTContext().getParents(*parent);
285 parent = parentsRange.begin() == parentsRange.end() ? nullptr
286 : parentsRange.begin()->get<Stmt>();
290 if (!parent)
292 // check if we have an expression like
293 // int& r = var;
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;
306 break;
308 if (isa<CXXReinterpretCastExpr>(parent))
310 // once we see one of these, there is not much useful we can know
311 bCannotBeConst = true;
312 break;
314 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
315 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
316 || isa<ExprWithCleanups>(parent))
318 walkUp();
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
324 || op == UO_PreDec)
326 bCannotBeConst = true;
328 walkUp();
330 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
332 auto callee = getCallee(operatorCallExpr);
333 if (callee)
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;
347 else
348 bCannotBeConst = true; // conservative, could improve
349 walkUp();
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;
366 break;
368 if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
369 CalleeWrapper(calleeMethodDecl)))
370 bCannotBeConst = true;
372 else
373 bCannotBeConst = true; // can happen in templates
374 walkUp();
376 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
378 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
379 CalleeWrapper(cxxConstructExpr)))
380 bCannotBeConst = true;
381 walkUp();
383 else if (auto callExpr = dyn_cast<CallExpr>(parent))
385 auto callee = getCallee(callExpr);
386 if (callee)
388 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
389 bCannotBeConst = true;
391 else
392 bCannotBeConst = true; // conservative, could improve
393 walkUp();
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;
403 if (assignmentOp)
405 if (binaryOp->getLHS() == child)
406 bCannotBeConst = true;
407 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
408 .LvalueReference()
409 .NonConst())
410 // if the LHS is a non-const reference, we could write to the var later on
411 bCannotBeConst = true;
413 walkUp();
415 else if (isa<ReturnStmt>(parent))
417 if (insideFunctionDecl)
419 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
420 if (tc.LvalueReference().NonConst())
421 bCannotBeConst = true;
423 break;
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))
429 break;
431 else
433 walkUp();
435 } while (true);
437 if (bDump)
439 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
440 compat::getBeginLoc(memberExpr))
441 << bCannotBeConst << memberExpr->getSourceRange();
442 if (parent)
444 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
445 << parent->getSourceRange();
446 parent->dump();
448 memberExpr->dump();
449 varDecl->getType()->dump();
452 if (bCannotBeConst)
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())
467 return true;
469 else
471 for (unsigned i = 0; i < len; ++i)
472 if (callExpr.getArg(i) == child)
473 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
474 .LvalueReference()
475 .NonConst())
476 return true;
478 return false;
481 llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr)
483 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
484 if (functionDecl)
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);
506 #endif
508 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */