Move setting of LD_LIBRARY_PATH closer to invocation of cppunittester
[LibreOffice.git] / compilerplugins / clang / store / constfields.cxx
blob692c84daeb8cc7ccbaae74d16b2f8d746ca39e36
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 "check.hxx"
27 #include "clang/AST/ParentMapContext.h"
29 /**
30 Look for fields that are only assigned to in the constructor using field-init, and can therefore be const.
32 The process goes something like this:
33 $ make check
34 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='constfields' check
35 $ ./compilerplugins/clang/constfields.py
37 and then
38 $ for dir in *; do make $dir FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='constfieldsrewrite' $dir; done
39 to auto-remove the method declarations
43 namespace
45 struct MyFieldInfo
47 const RecordDecl* parentRecord;
48 std::string parentClass;
49 std::string fieldName;
50 std::string fieldType;
51 std::string sourceLocation;
52 std::string access;
54 bool operator<(const MyFieldInfo& lhs, const MyFieldInfo& rhs)
56 return std::tie(lhs.parentClass, lhs.fieldName) < std::tie(rhs.parentClass, rhs.fieldName);
59 // try to limit the voluminous output a little
60 static std::set<MyFieldInfo> cannotBeConstSet;
61 static std::set<MyFieldInfo> definitionSet;
63 /**
64 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
66 class CallerWrapper
68 const CallExpr* m_callExpr;
69 const CXXConstructExpr* m_cxxConstructExpr;
71 public:
72 CallerWrapper(const CallExpr* callExpr)
73 : m_callExpr(callExpr)
74 , m_cxxConstructExpr(nullptr)
77 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
78 : m_callExpr(nullptr)
79 , m_cxxConstructExpr(cxxConstructExpr)
82 unsigned getNumArgs() const
84 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
86 const Expr* getArg(unsigned i) const
88 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
91 class CalleeWrapper
93 const FunctionDecl* m_calleeFunctionDecl = nullptr;
94 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
95 const FunctionProtoType* m_functionPrototype = nullptr;
97 public:
98 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
99 : m_calleeFunctionDecl(calleeFunctionDecl)
102 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
103 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
106 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
107 : m_functionPrototype(functionPrototype)
110 unsigned getNumParams() const
112 if (m_calleeFunctionDecl)
113 return m_calleeFunctionDecl->getNumParams();
114 else if (m_cxxConstructorDecl)
115 return m_cxxConstructorDecl->getNumParams();
116 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
117 // FunctionProtoType will assert if we call getParamTypes() and it has no params
118 return 0;
119 else
120 return m_functionPrototype->getParamTypes().size();
122 const QualType getParamType(unsigned i) const
124 if (m_calleeFunctionDecl)
125 return m_calleeFunctionDecl->getParamDecl(i)->getType();
126 else if (m_cxxConstructorDecl)
127 return m_cxxConstructorDecl->getParamDecl(i)->getType();
128 else
129 return m_functionPrototype->getParamTypes()[i];
131 std::string getNameAsString() const
133 if (m_calleeFunctionDecl)
134 return m_calleeFunctionDecl->getNameAsString();
135 else if (m_cxxConstructorDecl)
136 return m_cxxConstructorDecl->getNameAsString();
137 else
138 return "";
140 CXXMethodDecl const* getAsCXXMethodDecl() const
142 if (m_calleeFunctionDecl)
143 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
144 return nullptr;
148 class ConstFields : public RecursiveASTVisitor<ConstFields>, public loplugin::Plugin
150 public:
151 explicit ConstFields(loplugin::InstantiationData const& data)
152 : Plugin(data)
156 virtual void run() override;
158 bool shouldVisitTemplateInstantiations() const { return true; }
159 bool shouldVisitImplicitCode() const { return true; }
161 bool VisitFieldDecl(const FieldDecl*);
162 bool VisitMemberExpr(const MemberExpr*);
163 bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
164 bool TraverseCXXMethodDecl(CXXMethodDecl*);
165 bool TraverseFunctionDecl(FunctionDecl*);
166 bool TraverseIfStmt(IfStmt*);
168 private:
169 MyFieldInfo niceName(const FieldDecl*);
170 void check(const FieldDecl* fieldDecl, const Expr* memberExpr);
171 bool isSomeKindOfZero(const Expr* arg);
172 bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
173 CalleeWrapper calleeFunctionDecl);
174 llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
176 RecordDecl* insideMoveOrCopyDeclParent = nullptr;
177 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
178 // we store the parent function on the way down the AST.
179 FunctionDecl* insideFunctionDecl = nullptr;
180 std::vector<FieldDecl const*> insideConditionalCheckOfMemberSet;
183 void ConstFields::run()
185 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
187 if (!isUnitTestMode())
189 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
190 // writing to the same logfile
191 std::string output;
192 for (const MyFieldInfo& s : cannotBeConstSet)
193 output += "write-outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
194 for (const MyFieldInfo& s : definitionSet)
195 output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t"
196 + s.fieldType + "\t" + s.sourceLocation + "\n";
197 std::ofstream myfile;
198 myfile.open(WORKDIR "/loplugin.constfields.log", std::ios::app | std::ios::out);
199 myfile << output;
200 myfile.close();
202 else
204 for (const MyFieldInfo& s : cannotBeConstSet)
205 report(DiagnosticsEngine::Warning, "notconst %0", s.parentRecord->getBeginLoc())
206 << s.fieldName;
210 MyFieldInfo ConstFields::niceName(const FieldDecl* fieldDecl)
212 MyFieldInfo aInfo;
214 const RecordDecl* recordDecl = fieldDecl->getParent();
216 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
218 if (cxxRecordDecl->getTemplateInstantiationPattern())
219 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
220 aInfo.parentRecord = cxxRecordDecl;
221 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
223 else
225 aInfo.parentRecord = recordDecl;
226 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
229 aInfo.fieldName = fieldDecl->getNameAsString();
230 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
231 size_t idx = aInfo.fieldName.find(SRCDIR);
232 if (idx != std::string::npos)
234 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
236 aInfo.fieldType = fieldDecl->getType().getAsString();
238 SourceLocation expansionLoc
239 = compiler.getSourceManager().getExpansionLoc(fieldDecl->getLocation());
240 StringRef name = getFilenameOfLocation(expansionLoc);
241 aInfo.sourceLocation
242 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
243 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
244 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
246 switch (fieldDecl->getAccess())
248 case AS_public:
249 aInfo.access = "public";
250 break;
251 case AS_private:
252 aInfo.access = "private";
253 break;
254 case AS_protected:
255 aInfo.access = "protected";
256 break;
257 default:
258 aInfo.access = "unknown";
259 break;
262 return aInfo;
265 bool ConstFields::VisitFieldDecl(const FieldDecl* fieldDecl)
267 fieldDecl = fieldDecl->getCanonicalDecl();
268 if (ignoreLocation(fieldDecl))
270 return true;
272 // ignore stuff that forms part of the stable URE interface
273 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
275 return true;
277 definitionSet.insert(niceName(fieldDecl));
278 return true;
281 bool ConstFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
283 auto copy = insideMoveOrCopyDeclParent;
284 if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
286 if (cxxConstructorDecl->isCopyOrMoveConstructor())
287 insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent();
289 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
290 insideMoveOrCopyDeclParent = copy;
291 return ret;
294 bool ConstFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
296 auto copy1 = insideMoveOrCopyDeclParent;
297 auto copy2 = insideFunctionDecl;
298 if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
300 if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator())
301 insideMoveOrCopyDeclParent = cxxMethodDecl->getParent();
303 insideFunctionDecl = cxxMethodDecl;
304 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
305 insideMoveOrCopyDeclParent = copy1;
306 insideFunctionDecl = copy2;
307 return ret;
310 bool ConstFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
312 auto copy2 = insideFunctionDecl;
313 insideFunctionDecl = functionDecl;
314 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
315 insideFunctionDecl = copy2;
316 return ret;
319 bool ConstFields::TraverseIfStmt(IfStmt* ifStmt)
321 FieldDecl const* memberFieldDecl = nullptr;
322 if (Expr const* cond = ifStmt->getCond())
324 if (auto memberExpr = dyn_cast<MemberExpr>(cond->IgnoreParenImpCasts()))
326 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
327 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
330 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
331 if (memberFieldDecl)
332 insideConditionalCheckOfMemberSet.pop_back();
333 return ret;
336 bool ConstFields::VisitMemberExpr(const MemberExpr* memberExpr)
338 const ValueDecl* decl = memberExpr->getMemberDecl();
339 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
340 if (!fieldDecl)
342 return true;
344 fieldDecl = fieldDecl->getCanonicalDecl();
345 if (ignoreLocation(fieldDecl))
347 return true;
349 // ignore stuff that forms part of the stable URE interface
350 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
352 return true;
355 check(fieldDecl, memberExpr);
357 return true;
360 void ConstFields::check(const FieldDecl* fieldDecl, const Expr* memberExpr)
362 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
363 const Stmt* child = memberExpr;
364 const Stmt* parent
365 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
366 // walk up the tree until we find something interesting
367 bool bCannotBeConst = false;
368 bool bDump = false;
369 auto walkUp = [&]() {
370 child = parent;
371 auto parentsRange = compiler.getASTContext().getParents(*parent);
372 parent = parentsRange.begin() == parentsRange.end() ? nullptr
373 : parentsRange.begin()->get<Stmt>();
377 if (!parent)
379 // check if we have an expression like
380 // int& r = m_field;
381 auto parentsRange = compiler.getASTContext().getParents(*child);
382 if (parentsRange.begin() != parentsRange.end())
384 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
385 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
386 // which is of type 'T&&' and also an l-value-ref ?
387 if (varDecl && !varDecl->isImplicit()
388 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
390 bCannotBeConst = true;
393 break;
395 if (isa<CXXReinterpretCastExpr>(parent))
397 // once we see one of these, there is not much useful we can know
398 bCannotBeConst = true;
399 break;
401 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
402 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
403 || isa<ExprWithCleanups>(parent))
405 walkUp();
407 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
409 UnaryOperator::Opcode op = unaryOperator->getOpcode();
410 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
411 || op == UO_PreDec)
413 bCannotBeConst = true;
415 walkUp();
417 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
419 auto callee = getCallee(operatorCallExpr);
420 if (callee)
422 // if calling a non-const operator on the field
423 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
424 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child
425 && !calleeMethodDecl->isConst())
427 bCannotBeConst = true;
429 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
431 bCannotBeConst = true;
434 else
435 bCannotBeConst = true; // conservative, could improve
436 walkUp();
438 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
440 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
441 if (calleeMethodDecl)
443 // if calling a non-const method on the field
444 const Expr* tmp = dyn_cast<Expr>(child);
445 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
447 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
449 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp
450 && !calleeMethodDecl->isConst())
452 bCannotBeConst = true;
453 break;
455 if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr,
456 CalleeWrapper(calleeMethodDecl)))
457 bCannotBeConst = true;
459 else
460 bCannotBeConst = true; // can happen in templates
461 walkUp();
463 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
465 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
466 CalleeWrapper(cxxConstructExpr)))
467 bCannotBeConst = true;
468 walkUp();
470 else if (auto callExpr = dyn_cast<CallExpr>(parent))
472 auto callee = getCallee(callExpr);
473 if (callee)
475 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
476 bCannotBeConst = true;
478 else
479 bCannotBeConst = true; // conservative, could improve
480 walkUp();
482 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
484 BinaryOperator::Opcode op = binaryOp->getOpcode();
485 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
486 || op == BO_RemAssign || op == BO_AddAssign
487 || op == BO_SubAssign || op == BO_ShlAssign
488 || op == BO_ShrAssign || op == BO_AndAssign
489 || op == BO_XorAssign || op == BO_OrAssign;
490 if (assignmentOp)
492 if (binaryOp->getLHS() == child)
493 bCannotBeConst = true;
494 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
495 .LvalueReference()
496 .NonConst())
497 // if the LHS is a non-const reference, we could write to the field later on
498 bCannotBeConst = true;
500 walkUp();
502 else if (isa<ReturnStmt>(parent))
504 if (insideFunctionDecl)
506 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
507 if (tc.LvalueReference().NonConst())
508 bCannotBeConst = true;
510 break;
512 else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
513 || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<CXXForRangeStmt>(parent)
514 || isa<DefaultStmt>(parent))
516 break;
518 else
520 walkUp();
522 } while (true);
524 if (bDump)
526 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
527 memberExpr->getBeginLoc())
528 << bCannotBeConst << memberExpr->getSourceRange();
529 if (parent)
531 report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
532 << parent->getSourceRange();
533 parent->dump();
535 memberExpr->dump();
536 fieldDecl->getType()->dump();
539 if (bCannotBeConst)
541 cannotBeConstSet.insert(niceName(fieldDecl));
545 bool ConstFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child,
546 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
548 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
549 // if it's an array, passing it by value to a method typically means the
550 // callee takes a pointer and can modify the array
551 if (fieldDecl->getType()->isConstantArrayType())
553 for (unsigned i = 0; i < len; ++i)
554 if (callExpr.getArg(i) == child)
555 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
556 return true;
558 else
560 for (unsigned i = 0; i < len; ++i)
561 if (callExpr.getArg(i) == child)
562 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
563 .LvalueReference()
564 .NonConst())
565 return true;
567 return false;
570 llvm::Optional<CalleeWrapper> ConstFields::getCallee(CallExpr const* callExpr)
572 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
573 if (functionDecl)
574 return CalleeWrapper(functionDecl);
576 // Extract the functionprototype from a type
577 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
578 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
580 if (auto prototype = pointerType->getPointeeType()
581 ->getUnqualifiedDesugaredType()
582 ->getAs<FunctionProtoType>())
584 return CalleeWrapper(prototype);
588 return llvm::Optional<CalleeWrapper>();
591 loplugin::Plugin::Registration<ConstFields> X("constfields", false);
594 #endif
596 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */