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