1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
11 
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"
24 
25 /**
26 Look for static vars that are only assigned to once, and never written to, they can be const.
27 */
28 
29 namespace
30 {
31 /**
32  * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
33  */
34 class CallerWrapper
35 {
36     const CallExpr* m_callExpr;
37     const CXXConstructExpr* m_cxxConstructExpr;
38 
39 public:
CallerWrapper(const CallExpr * callExpr)40     CallerWrapper(const CallExpr* callExpr)
41         : m_callExpr(callExpr)
42         , m_cxxConstructExpr(nullptr)
43     {
44     }
CallerWrapper(const CXXConstructExpr * cxxConstructExpr)45     CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
46         : m_callExpr(nullptr)
47         , m_cxxConstructExpr(cxxConstructExpr)
48     {
49     }
getNumArgs() const50     unsigned getNumArgs() const
51     {
52         return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
53     }
getArg(unsigned i) const54     const Expr* getArg(unsigned i) const
55     {
56         return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
57     }
58 };
59 class CalleeWrapper
60 {
61     const FunctionDecl* m_calleeFunctionDecl = nullptr;
62     const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
63     const FunctionProtoType* m_functionPrototype = nullptr;
64 
65 public:
CalleeWrapper(const FunctionDecl * calleeFunctionDecl)66     explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
67         : m_calleeFunctionDecl(calleeFunctionDecl)
68     {
69     }
CalleeWrapper(const CXXConstructExpr * cxxConstructExpr)70     explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
71         : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
72     {
73     }
CalleeWrapper(const FunctionProtoType * functionPrototype)74     explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
75         : m_functionPrototype(functionPrototype)
76     {
77     }
getNumParams() const78     unsigned getNumParams() const
79     {
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();
89     }
getParamType(unsigned i) const90     const QualType getParamType(unsigned i) const
91     {
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];
98     }
getNameAsString() const99     std::string getNameAsString() const
100     {
101         if (m_calleeFunctionDecl)
102             return m_calleeFunctionDecl->getNameAsString();
103         else if (m_cxxConstructorDecl)
104             return m_cxxConstructorDecl->getNameAsString();
105         else
106             return "";
107     }
getAsCXXMethodDecl() const108     CXXMethodDecl const* getAsCXXMethodDecl() const
109     {
110         if (m_calleeFunctionDecl)
111             return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
112         return nullptr;
113     }
114 };
115 
116 class ConstVars : public RecursiveASTVisitor<ConstVars>, public loplugin::Plugin
117 {
118 public:
ConstVars(loplugin::InstantiationData const & data)119     explicit ConstVars(loplugin::InstantiationData const& data)
120         : Plugin(data)
121     {
122     }
123 
124     virtual void run() override;
125 
shouldVisitTemplateInstantiations() const126     bool shouldVisitTemplateInstantiations() const { return true; }
shouldVisitImplicitCode() const127     bool shouldVisitImplicitCode() const { return true; }
128 
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*);
136 
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*);
143 
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;
149 
150     std::set<VarDecl const*> cannotBeConstSet;
151     std::set<VarDecl const*> definitionSet;
152 };
153 
run()154 void ConstVars::run()
155 {
156     // clang::Expr::isCXX11ConstantExpr only works for C++
157     if (!compiler.getLangOpts().CPlusPlus)
158         return;
159 
160     TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
161 
162     SourceManager& SM = compiler.getSourceManager();
163     for (VarDecl const* v : definitionSet)
164     {
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));
172     }
173 }
174 
VisitVarDecl(const VarDecl * varDecl)175 bool ConstVars::VisitVarDecl(const VarDecl* varDecl)
176 {
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;
195 
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;
202 
203     definitionSet.insert(varDecl);
204     return true;
205 }
206 
VisitCXXForRangeStmt(const CXXForRangeStmt * forStmt)207 bool ConstVars::VisitCXXForRangeStmt(const CXXForRangeStmt* forStmt)
208 {
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;
222 
223     definitionSet.insert(varDecl);
224     return true;
225 }
226 
TraverseCXXConstructorDecl(CXXConstructorDecl * cxxConstructorDecl)227 bool ConstVars::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
228 {
229     auto copy = insideMoveOrCopyDeclParent;
230     if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
231     {
232         if (cxxConstructorDecl->isCopyOrMoveConstructor())
233             insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent();
234     }
235     bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
236     insideMoveOrCopyDeclParent = copy;
237     return ret;
238 }
239 
TraverseCXXMethodDecl(CXXMethodDecl * cxxMethodDecl)240 bool ConstVars::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
241 {
242     auto copy1 = insideMoveOrCopyDeclParent;
243     auto copy2 = insideFunctionDecl;
244     if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
245     {
246         if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator())
247             insideMoveOrCopyDeclParent = cxxMethodDecl->getParent();
248     }
249     insideFunctionDecl = cxxMethodDecl;
250     bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
251     insideMoveOrCopyDeclParent = copy1;
252     insideFunctionDecl = copy2;
253     return ret;
254 }
255 
TraverseFunctionDecl(FunctionDecl * functionDecl)256 bool ConstVars::TraverseFunctionDecl(FunctionDecl* functionDecl)
257 {
258     auto copy2 = insideFunctionDecl;
259     insideFunctionDecl = functionDecl;
260     bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
261     insideFunctionDecl = copy2;
262     return ret;
263 }
264 
TraverseIfStmt(IfStmt * ifStmt)265 bool ConstVars::TraverseIfStmt(IfStmt* ifStmt)
266 {
267     VarDecl const* varDecl = nullptr;
268     Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
269     if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond))
270     {
271         if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
272             insideConditionalCheckOfVarSet.push_back(varDecl);
273     }
274     bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
275     if (varDecl)
276         insideConditionalCheckOfVarSet.pop_back();
277     return ret;
278 }
279 
VisitDeclRefExpr(const DeclRefExpr * declRefExpr)280 bool ConstVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
281 {
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;
291 
292     if (definitionSet.find(varDecl) != definitionSet.end())
293         check(varDecl, declRefExpr);
294 
295     return true;
296 }
297 
check(const VarDecl * varDecl,const Expr * memberExpr)298 void ConstVars::check(const VarDecl* varDecl, const Expr* memberExpr)
299 {
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>();
304 
305     // walk up the tree until we find something interesting
306 
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>();
314     };
315     do
316     {
317         if (!parent)
318         {
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())
323             {
324                 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
325                 if (varDecl)
326                 {
327                     if (varDecl->isImplicit())
328                     {
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>();
333                     }
334                     else if (loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
335                     {
336                         bCannotBeConst = true;
337                         break;
338                     }
339                 }
340             }
341         }
342         if (!parent)
343             break;
344         if (isa<CXXReinterpretCastExpr>(parent))
345         {
346             // once we see one of these, there is not much useful we can know
347             bCannotBeConst = true;
348             break;
349         }
350         else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
351                  || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
352                  || isa<ExprWithCleanups>(parent))
353         {
354             walkUp();
355         }
356         else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
357         {
358             UnaryOperator::Opcode op = unaryOperator->getOpcode();
359             if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
360                 || op == UO_PreDec)
361             {
362                 bCannotBeConst = true;
363             }
364             walkUp();
365         }
366         else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
367         {
368             auto callee = getCallee(operatorCallExpr);
369             if (callee)
370             {
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())
375                 {
376                     bCannotBeConst = true;
377                 }
378                 else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
379                 {
380                     bCannotBeConst = true;
381                 }
382             }
383             else
384                 bCannotBeConst = true; // conservative, could improve
385             walkUp();
386         }
387         else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
388         {
389             const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
390             if (calleeMethodDecl)
391             {
392                 // if calling a non-const method on the var
393                 const Expr* tmp = dyn_cast<Expr>(child);
394                 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
395                 {
396                     tmp = dyn_cast<MemberExpr>(tmp)->getBase();
397                 }
398                 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp
399                     && !calleeMethodDecl->isConst())
400                 {
401                     bCannotBeConst = true;
402                     break;
403                 }
404                 if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
405                                        CalleeWrapper(calleeMethodDecl)))
406                     bCannotBeConst = true;
407             }
408             else
409                 bCannotBeConst = true; // can happen in templates
410             walkUp();
411         }
412         else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
413         {
414             if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
415                                    CalleeWrapper(cxxConstructExpr)))
416                 bCannotBeConst = true;
417             walkUp();
418         }
419         else if (auto callExpr = dyn_cast<CallExpr>(parent))
420         {
421             auto callee = getCallee(callExpr);
422             if (callee)
423             {
424                 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
425                     bCannotBeConst = true;
426             }
427             else
428                 bCannotBeConst = true; // conservative, could improve
429             walkUp();
430         }
431         else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
432         {
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)
440             {
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;
448             }
449             walkUp();
450         }
451         else if (isa<ReturnStmt>(parent))
452         {
453             if (insideFunctionDecl)
454             {
455                 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
456                 if (tc.LvalueReference().NonConst())
457                     bCannotBeConst = true;
458             }
459             break;
460         }
461         else if (auto rangeStmt = dyn_cast<CXXForRangeStmt>(parent))
462         {
463             if (rangeStmt->getRangeStmt() == child)
464             {
465                 auto tc = loplugin::TypeCheck(rangeStmt->getLoopVariable()->getType());
466                 if (tc.LvalueReference().NonConst())
467                     bCannotBeConst = true;
468             }
469             break;
470         }
471         else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
472                  || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<DefaultStmt>(parent))
473         {
474             break;
475         }
476         else
477         {
478             walkUp();
479         }
480     } while (true);
481 
482     if (bDump)
483     {
484         report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
485                compat::getBeginLoc(memberExpr))
486             << bCannotBeConst << memberExpr->getSourceRange();
487         if (parent)
488         {
489             report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
490                 << parent->getSourceRange();
491             parent->dump();
492         }
493         memberExpr->dump();
494         varDecl->getType()->dump();
495     }
496 
497     if (bCannotBeConst)
498         cannotBeConstSet.insert(varDecl);
499 }
500 
IsPassedByNonConst(const VarDecl * varDecl,const Stmt * child,CallerWrapper callExpr,CalleeWrapper calleeFunctionDecl)501 bool ConstVars::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child,
502                                    CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
503 {
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())
508     {
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;
513     }
514     else
515     {
516         for (unsigned i = 0; i < len; ++i)
517             if (callExpr.getArg(i) == child)
518             {
519                 auto tc = loplugin::TypeCheck(calleeFunctionDecl.getParamType(i));
520                 if (tc.LvalueReference().NonConst() || tc.Pointer().NonConst())
521                     return true;
522             }
523     }
524     return false;
525 }
526 
getCallee(CallExpr const * callExpr)527 llvm::Optional<CalleeWrapper> ConstVars::getCallee(CallExpr const* callExpr)
528 {
529     FunctionDecl const* functionDecl = callExpr->getDirectCallee();
530     if (functionDecl)
531         return CalleeWrapper(functionDecl);
532 
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>())
536     {
537         if (auto prototype = pointerType->getPointeeType()
538                                  ->getUnqualifiedDesugaredType()
539                                  ->getAs<FunctionProtoType>())
540         {
541             return CalleeWrapper(prototype);
542         }
543     }
544 
545     return llvm::Optional<CalleeWrapper>();
546 }
547 
548 /** off by default because it is very expensive, it walks up the AST a lot */
549 loplugin::Plugin::Registration<ConstVars> X("constvars", false);
550 }
551 
552 #endif
553 
554 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
555