bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / constfields.cxx
blobce73e0d8e51fcbfc6faf06b2b4229599c55733b6
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 fields that are only assigned to in the constructor using field-init, and can therefore be const.
28 The process goes something like this:
29 $ make check
30 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='constfields' check
31 $ ./compilerplugins/clang/constfields.py
33 and then
34 $ for dir in *; do make $dir FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='constfieldsrewrite' $dir; done
35 to auto-remove the method declarations
39 namespace
41 struct MyFieldInfo
43 const RecordDecl* parentRecord;
44 std::string parentClass;
45 std::string fieldName;
46 std::string fieldType;
47 std::string sourceLocation;
48 std::string access;
50 bool operator<(const MyFieldInfo& lhs, const MyFieldInfo& rhs)
52 return std::tie(lhs.parentClass, lhs.fieldName) < std::tie(rhs.parentClass, rhs.fieldName);
55 // try to limit the voluminous output a little
56 static std::set<MyFieldInfo> cannotBeConstSet;
57 static std::set<MyFieldInfo> definitionSet;
59 /**
60 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
62 class CallerWrapper
64 const CallExpr* m_callExpr;
65 const CXXConstructExpr* m_cxxConstructExpr;
67 public:
68 CallerWrapper(const CallExpr* callExpr)
69 : m_callExpr(callExpr)
70 , m_cxxConstructExpr(nullptr)
73 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
74 : m_callExpr(nullptr)
75 , m_cxxConstructExpr(cxxConstructExpr)
78 unsigned getNumArgs() const
80 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
82 const Expr* getArg(unsigned i) const
84 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
87 class CalleeWrapper
89 const FunctionDecl* m_calleeFunctionDecl = nullptr;
90 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
91 const FunctionProtoType* m_functionPrototype = nullptr;
93 public:
94 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
95 : m_calleeFunctionDecl(calleeFunctionDecl)
98 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
99 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
102 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
103 : m_functionPrototype(functionPrototype)
106 unsigned getNumParams() const
108 if (m_calleeFunctionDecl)
109 return m_calleeFunctionDecl->getNumParams();
110 else if (m_cxxConstructorDecl)
111 return m_cxxConstructorDecl->getNumParams();
112 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
113 // FunctionProtoType will assert if we call getParamTypes() and it has no params
114 return 0;
115 else
116 return m_functionPrototype->getParamTypes().size();
118 const QualType getParamType(unsigned i) const
120 if (m_calleeFunctionDecl)
121 return m_calleeFunctionDecl->getParamDecl(i)->getType();
122 else if (m_cxxConstructorDecl)
123 return m_cxxConstructorDecl->getParamDecl(i)->getType();
124 else
125 return m_functionPrototype->getParamTypes()[i];
127 std::string getNameAsString() const
129 if (m_calleeFunctionDecl)
130 return m_calleeFunctionDecl->getNameAsString();
131 else if (m_cxxConstructorDecl)
132 return m_cxxConstructorDecl->getNameAsString();
133 else
134 return "";
136 CXXMethodDecl const* getAsCXXMethodDecl() const
138 if (m_calleeFunctionDecl)
139 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
140 return nullptr;
144 class ConstFields : public RecursiveASTVisitor<ConstFields>, public loplugin::Plugin
146 public:
147 explicit ConstFields(loplugin::InstantiationData const& data)
148 : Plugin(data)
152 virtual void run() override;
154 bool shouldVisitTemplateInstantiations() const { return true; }
155 bool shouldVisitImplicitCode() const { return true; }
157 bool VisitFieldDecl(const FieldDecl*);
158 bool VisitMemberExpr(const MemberExpr*);
159 bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
160 bool TraverseCXXMethodDecl(CXXMethodDecl*);
161 bool TraverseFunctionDecl(FunctionDecl*);
162 bool TraverseIfStmt(IfStmt*);
164 private:
165 MyFieldInfo niceName(const FieldDecl*);
166 void check(const FieldDecl* fieldDecl, const Expr* memberExpr);
167 bool isSomeKindOfZero(const Expr* arg);
168 bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
169 CalleeWrapper calleeFunctionDecl);
170 llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
172 RecordDecl* insideMoveOrCopyDeclParent = nullptr;
173 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
174 // we store the parent function on the way down the AST.
175 FunctionDecl* insideFunctionDecl = nullptr;
176 std::vector<FieldDecl const*> insideConditionalCheckOfMemberSet;
179 void ConstFields::run()
181 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
183 if (!isUnitTestMode())
185 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
186 // writing to the same logfile
187 std::string output;
188 for (const MyFieldInfo& s : cannotBeConstSet)
189 output += "write-outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
190 for (const MyFieldInfo& s : definitionSet)
191 output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t"
192 + s.fieldType + "\t" + s.sourceLocation + "\n";
193 std::ofstream myfile;
194 myfile.open(WORKDIR "/loplugin.constfields.log", std::ios::app | std::ios::out);
195 myfile << output;
196 myfile.close();
198 else
200 for (const MyFieldInfo& s : cannotBeConstSet)
201 report(DiagnosticsEngine::Warning, "notconst %0", compat::getBeginLoc(s.parentRecord))
202 << s.fieldName;
206 MyFieldInfo ConstFields::niceName(const FieldDecl* fieldDecl)
208 MyFieldInfo aInfo;
210 const RecordDecl* recordDecl = fieldDecl->getParent();
212 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
214 if (cxxRecordDecl->getTemplateInstantiationPattern())
215 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
216 aInfo.parentRecord = cxxRecordDecl;
217 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
219 else
221 aInfo.parentRecord = recordDecl;
222 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
225 aInfo.fieldName = fieldDecl->getNameAsString();
226 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
227 size_t idx = aInfo.fieldName.find(SRCDIR);
228 if (idx != std::string::npos)
230 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
232 aInfo.fieldType = fieldDecl->getType().getAsString();
234 SourceLocation expansionLoc
235 = compiler.getSourceManager().getExpansionLoc(fieldDecl->getLocation());
236 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
237 aInfo.sourceLocation
238 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
239 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
240 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
242 switch (fieldDecl->getAccess())
244 case AS_public:
245 aInfo.access = "public";
246 break;
247 case AS_private:
248 aInfo.access = "private";
249 break;
250 case AS_protected:
251 aInfo.access = "protected";
252 break;
253 default:
254 aInfo.access = "unknown";
255 break;
258 return aInfo;
261 bool ConstFields::VisitFieldDecl(const FieldDecl* fieldDecl)
263 fieldDecl = fieldDecl->getCanonicalDecl();
264 if (ignoreLocation(fieldDecl))
266 return true;
268 // ignore stuff that forms part of the stable URE interface
269 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
271 return true;
273 definitionSet.insert(niceName(fieldDecl));
274 return true;
277 bool ConstFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
279 auto copy = insideMoveOrCopyDeclParent;
280 if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
282 if (cxxConstructorDecl->isCopyOrMoveConstructor())
283 insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent();
285 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
286 insideMoveOrCopyDeclParent = copy;
287 return ret;
290 bool ConstFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
292 auto copy1 = insideMoveOrCopyDeclParent;
293 auto copy2 = insideFunctionDecl;
294 if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
296 if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator())
297 insideMoveOrCopyDeclParent = cxxMethodDecl->getParent();
299 insideFunctionDecl = cxxMethodDecl;
300 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
301 insideMoveOrCopyDeclParent = copy1;
302 insideFunctionDecl = copy2;
303 return ret;
306 bool ConstFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
308 auto copy2 = insideFunctionDecl;
309 insideFunctionDecl = functionDecl;
310 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
311 insideFunctionDecl = copy2;
312 return ret;
315 bool ConstFields::TraverseIfStmt(IfStmt* ifStmt)
317 FieldDecl const* memberFieldDecl = nullptr;
318 Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
319 if (auto memberExpr = dyn_cast<MemberExpr>(cond))
321 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
322 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
324 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
325 if (memberFieldDecl)
326 insideConditionalCheckOfMemberSet.pop_back();
327 return ret;
330 bool ConstFields::VisitMemberExpr(const MemberExpr* memberExpr)
332 const ValueDecl* decl = memberExpr->getMemberDecl();
333 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
334 if (!fieldDecl)
336 return true;
338 fieldDecl = fieldDecl->getCanonicalDecl();
339 if (ignoreLocation(fieldDecl))
341 return true;
343 // ignore stuff that forms part of the stable URE interface
344 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
346 return true;
349 check(fieldDecl, memberExpr);
351 return true;
354 void ConstFields::check(const FieldDecl* fieldDecl, const Expr* memberExpr)
356 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
357 const Stmt* child = memberExpr;
358 const Stmt* parent
359 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
360 // walk up the tree until we find something interesting
361 bool bCannotBeConst = false;
362 bool bDump = false;
363 auto walkUp = [&]() {
364 child = parent;
365 auto parentsRange = compiler.getASTContext().getParents(*parent);
366 parent = parentsRange.begin() == parentsRange.end() ? nullptr
367 : parentsRange.begin()->get<Stmt>();
371 if (!parent)
373 // check if we have an expression like
374 // int& r = m_field;
375 auto parentsRange = compiler.getASTContext().getParents(*child);
376 if (parentsRange.begin() != parentsRange.end())
378 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
379 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
380 // which is of type 'T&&' and also an l-value-ref ?
381 if (varDecl && !varDecl->isImplicit()
382 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
384 bCannotBeConst = true;
387 break;
389 if (isa<CXXReinterpretCastExpr>(parent))
391 // once we see one of these, there is not much useful we can know
392 bCannotBeConst = true;
393 break;
395 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
396 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
397 || isa<ExprWithCleanups>(parent))
399 walkUp();
401 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
403 UnaryOperator::Opcode op = unaryOperator->getOpcode();
404 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
405 || op == UO_PreDec)
407 bCannotBeConst = true;
409 walkUp();
411 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
413 auto callee = getCallee(operatorCallExpr);
414 if (callee)
416 // if calling a non-const operator on the field
417 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
418 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child
419 && !calleeMethodDecl->isConst())
421 bCannotBeConst = true;
423 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
425 bCannotBeConst = true;
428 else
429 bCannotBeConst = true; // conservative, could improve
430 walkUp();
432 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
434 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
435 if (calleeMethodDecl)
437 // if calling a non-const method on the field
438 const Expr* tmp = dyn_cast<Expr>(child);
439 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
441 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
443 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp
444 && !calleeMethodDecl->isConst())
446 bCannotBeConst = true;
447 break;
449 if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr,
450 CalleeWrapper(calleeMethodDecl)))
451 bCannotBeConst = true;
453 else
454 bCannotBeConst = true; // can happen in templates
455 walkUp();
457 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
459 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
460 CalleeWrapper(cxxConstructExpr)))
461 bCannotBeConst = true;
462 walkUp();
464 else if (auto callExpr = dyn_cast<CallExpr>(parent))
466 auto callee = getCallee(callExpr);
467 if (callee)
469 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
470 bCannotBeConst = true;
472 else
473 bCannotBeConst = true; // conservative, could improve
474 walkUp();
476 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
478 BinaryOperator::Opcode op = binaryOp->getOpcode();
479 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
480 || op == BO_RemAssign || op == BO_AddAssign
481 || op == BO_SubAssign || op == BO_ShlAssign
482 || op == BO_ShrAssign || op == BO_AndAssign
483 || op == BO_XorAssign || op == BO_OrAssign;
484 if (assignmentOp)
486 if (binaryOp->getLHS() == child)
487 bCannotBeConst = true;
488 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
489 .LvalueReference()
490 .NonConst())
491 // if the LHS is a non-const reference, we could write to the field later on
492 bCannotBeConst = true;
494 walkUp();
496 else if (isa<ReturnStmt>(parent))
498 if (insideFunctionDecl)
500 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
501 if (tc.LvalueReference().NonConst())
502 bCannotBeConst = true;
504 break;
506 else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
507 || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<CXXForRangeStmt>(parent)
508 || isa<DefaultStmt>(parent))
510 break;
512 else
514 walkUp();
516 } while (true);
518 if (bDump)
520 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
521 compat::getBeginLoc(memberExpr))
522 << bCannotBeConst << memberExpr->getSourceRange();
523 if (parent)
525 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
526 << parent->getSourceRange();
527 parent->dump();
529 memberExpr->dump();
530 fieldDecl->getType()->dump();
533 if (bCannotBeConst)
535 cannotBeConstSet.insert(niceName(fieldDecl));
539 bool ConstFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child,
540 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
542 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
543 // if it's an array, passing it by value to a method typically means the
544 // callee takes a pointer and can modify the array
545 if (fieldDecl->getType()->isConstantArrayType())
547 for (unsigned i = 0; i < len; ++i)
548 if (callExpr.getArg(i) == child)
549 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
550 return true;
552 else
554 for (unsigned i = 0; i < len; ++i)
555 if (callExpr.getArg(i) == child)
556 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
557 .LvalueReference()
558 .NonConst())
559 return true;
561 return false;
564 llvm::Optional<CalleeWrapper> ConstFields::getCallee(CallExpr const* callExpr)
566 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
567 if (functionDecl)
568 return CalleeWrapper(functionDecl);
570 // Extract the functionprototype from a type
571 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
572 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
574 if (auto prototype = pointerType->getPointeeType()
575 ->getUnqualifiedDesugaredType()
576 ->getAs<FunctionProtoType>())
578 return CalleeWrapper(prototype);
582 return llvm::Optional<CalleeWrapper>();
585 loplugin::Plugin::Registration<ConstFields> X("constfields", false);
588 #endif
590 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */