nss: upgrade to release 3.73
[LibreOffice.git] / compilerplugins / clang / constfields.cxx
blob635774810717af7dc31572fe9883d2fcd3851d53
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 "compat.hxx"
26 #include "check.hxx"
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
30 #endif
32 /**
33 Look for fields that are only assigned to in the constructor using field-init, and can therefore be const.
35 The process goes something like this:
36 $ make check
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='constfields' check
38 $ ./compilerplugins/clang/constfields.py
40 and then
41 $ for dir in *; do make $dir FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='constfieldsrewrite' $dir; done
42 to auto-remove the method declarations
46 namespace
48 struct MyFieldInfo
50 const RecordDecl* parentRecord;
51 std::string parentClass;
52 std::string fieldName;
53 std::string fieldType;
54 std::string sourceLocation;
55 std::string access;
57 bool operator<(const MyFieldInfo& lhs, const MyFieldInfo& rhs)
59 return std::tie(lhs.parentClass, lhs.fieldName) < std::tie(rhs.parentClass, rhs.fieldName);
62 // try to limit the voluminous output a little
63 static std::set<MyFieldInfo> cannotBeConstSet;
64 static std::set<MyFieldInfo> definitionSet;
66 /**
67 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
69 class CallerWrapper
71 const CallExpr* m_callExpr;
72 const CXXConstructExpr* m_cxxConstructExpr;
74 public:
75 CallerWrapper(const CallExpr* callExpr)
76 : m_callExpr(callExpr)
77 , m_cxxConstructExpr(nullptr)
80 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
81 : m_callExpr(nullptr)
82 , m_cxxConstructExpr(cxxConstructExpr)
85 unsigned getNumArgs() const
87 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
89 const Expr* getArg(unsigned i) const
91 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
94 class CalleeWrapper
96 const FunctionDecl* m_calleeFunctionDecl = nullptr;
97 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
98 const FunctionProtoType* m_functionPrototype = nullptr;
100 public:
101 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
102 : m_calleeFunctionDecl(calleeFunctionDecl)
105 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
106 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
109 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
110 : m_functionPrototype(functionPrototype)
113 unsigned getNumParams() const
115 if (m_calleeFunctionDecl)
116 return m_calleeFunctionDecl->getNumParams();
117 else if (m_cxxConstructorDecl)
118 return m_cxxConstructorDecl->getNumParams();
119 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
120 // FunctionProtoType will assert if we call getParamTypes() and it has no params
121 return 0;
122 else
123 return m_functionPrototype->getParamTypes().size();
125 const QualType getParamType(unsigned i) const
127 if (m_calleeFunctionDecl)
128 return m_calleeFunctionDecl->getParamDecl(i)->getType();
129 else if (m_cxxConstructorDecl)
130 return m_cxxConstructorDecl->getParamDecl(i)->getType();
131 else
132 return m_functionPrototype->getParamTypes()[i];
134 std::string getNameAsString() const
136 if (m_calleeFunctionDecl)
137 return m_calleeFunctionDecl->getNameAsString();
138 else if (m_cxxConstructorDecl)
139 return m_cxxConstructorDecl->getNameAsString();
140 else
141 return "";
143 CXXMethodDecl const* getAsCXXMethodDecl() const
145 if (m_calleeFunctionDecl)
146 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
147 return nullptr;
151 class ConstFields : public RecursiveASTVisitor<ConstFields>, public loplugin::Plugin
153 public:
154 explicit ConstFields(loplugin::InstantiationData const& data)
155 : Plugin(data)
159 virtual void run() override;
161 bool shouldVisitTemplateInstantiations() const { return true; }
162 bool shouldVisitImplicitCode() const { return true; }
164 bool VisitFieldDecl(const FieldDecl*);
165 bool VisitMemberExpr(const MemberExpr*);
166 bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
167 bool TraverseCXXMethodDecl(CXXMethodDecl*);
168 bool TraverseFunctionDecl(FunctionDecl*);
169 bool TraverseIfStmt(IfStmt*);
171 private:
172 MyFieldInfo niceName(const FieldDecl*);
173 void check(const FieldDecl* fieldDecl, const Expr* memberExpr);
174 bool isSomeKindOfZero(const Expr* arg);
175 bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child, CallerWrapper callExpr,
176 CalleeWrapper calleeFunctionDecl);
177 llvm::Optional<CalleeWrapper> getCallee(CallExpr const*);
179 RecordDecl* insideMoveOrCopyDeclParent = nullptr;
180 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
181 // we store the parent function on the way down the AST.
182 FunctionDecl* insideFunctionDecl = nullptr;
183 std::vector<FieldDecl const*> insideConditionalCheckOfMemberSet;
186 void ConstFields::run()
188 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
190 if (!isUnitTestMode())
192 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
193 // writing to the same logfile
194 std::string output;
195 for (const MyFieldInfo& s : cannotBeConstSet)
196 output += "write-outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
197 for (const MyFieldInfo& s : definitionSet)
198 output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t"
199 + s.fieldType + "\t" + s.sourceLocation + "\n";
200 std::ofstream myfile;
201 myfile.open(WORKDIR "/loplugin.constfields.log", std::ios::app | std::ios::out);
202 myfile << output;
203 myfile.close();
205 else
207 for (const MyFieldInfo& s : cannotBeConstSet)
208 report(DiagnosticsEngine::Warning, "notconst %0", compat::getBeginLoc(s.parentRecord))
209 << s.fieldName;
213 MyFieldInfo ConstFields::niceName(const FieldDecl* fieldDecl)
215 MyFieldInfo aInfo;
217 const RecordDecl* recordDecl = fieldDecl->getParent();
219 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
221 if (cxxRecordDecl->getTemplateInstantiationPattern())
222 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
223 aInfo.parentRecord = cxxRecordDecl;
224 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
226 else
228 aInfo.parentRecord = recordDecl;
229 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
232 aInfo.fieldName = fieldDecl->getNameAsString();
233 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
234 size_t idx = aInfo.fieldName.find(SRCDIR);
235 if (idx != std::string::npos)
237 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
239 aInfo.fieldType = fieldDecl->getType().getAsString();
241 SourceLocation expansionLoc
242 = compiler.getSourceManager().getExpansionLoc(fieldDecl->getLocation());
243 StringRef name = getFilenameOfLocation(expansionLoc);
244 aInfo.sourceLocation
245 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
246 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
247 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
249 switch (fieldDecl->getAccess())
251 case AS_public:
252 aInfo.access = "public";
253 break;
254 case AS_private:
255 aInfo.access = "private";
256 break;
257 case AS_protected:
258 aInfo.access = "protected";
259 break;
260 default:
261 aInfo.access = "unknown";
262 break;
265 return aInfo;
268 bool ConstFields::VisitFieldDecl(const FieldDecl* fieldDecl)
270 fieldDecl = fieldDecl->getCanonicalDecl();
271 if (ignoreLocation(fieldDecl))
273 return true;
275 // ignore stuff that forms part of the stable URE interface
276 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
278 return true;
280 definitionSet.insert(niceName(fieldDecl));
281 return true;
284 bool ConstFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
286 auto copy = insideMoveOrCopyDeclParent;
287 if (!ignoreLocation(cxxConstructorDecl) && cxxConstructorDecl->isThisDeclarationADefinition())
289 if (cxxConstructorDecl->isCopyOrMoveConstructor())
290 insideMoveOrCopyDeclParent = cxxConstructorDecl->getParent();
292 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
293 insideMoveOrCopyDeclParent = copy;
294 return ret;
297 bool ConstFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
299 auto copy1 = insideMoveOrCopyDeclParent;
300 auto copy2 = insideFunctionDecl;
301 if (!ignoreLocation(cxxMethodDecl) && cxxMethodDecl->isThisDeclarationADefinition())
303 if (cxxMethodDecl->isCopyAssignmentOperator() || cxxMethodDecl->isMoveAssignmentOperator())
304 insideMoveOrCopyDeclParent = cxxMethodDecl->getParent();
306 insideFunctionDecl = cxxMethodDecl;
307 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
308 insideMoveOrCopyDeclParent = copy1;
309 insideFunctionDecl = copy2;
310 return ret;
313 bool ConstFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
315 auto copy2 = insideFunctionDecl;
316 insideFunctionDecl = functionDecl;
317 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
318 insideFunctionDecl = copy2;
319 return ret;
322 bool ConstFields::TraverseIfStmt(IfStmt* ifStmt)
324 FieldDecl const* memberFieldDecl = nullptr;
325 Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
326 if (auto memberExpr = dyn_cast<MemberExpr>(cond))
328 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
329 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
331 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
332 if (memberFieldDecl)
333 insideConditionalCheckOfMemberSet.pop_back();
334 return ret;
337 bool ConstFields::VisitMemberExpr(const MemberExpr* memberExpr)
339 const ValueDecl* decl = memberExpr->getMemberDecl();
340 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
341 if (!fieldDecl)
343 return true;
345 fieldDecl = fieldDecl->getCanonicalDecl();
346 if (ignoreLocation(fieldDecl))
348 return true;
350 // ignore stuff that forms part of the stable URE interface
351 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
353 return true;
356 check(fieldDecl, memberExpr);
358 return true;
361 void ConstFields::check(const FieldDecl* fieldDecl, const Expr* memberExpr)
363 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
364 const Stmt* child = memberExpr;
365 const Stmt* parent
366 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
367 // walk up the tree until we find something interesting
368 bool bCannotBeConst = false;
369 bool bDump = false;
370 auto walkUp = [&]() {
371 child = parent;
372 auto parentsRange = compiler.getASTContext().getParents(*parent);
373 parent = parentsRange.begin() == parentsRange.end() ? nullptr
374 : parentsRange.begin()->get<Stmt>();
378 if (!parent)
380 // check if we have an expression like
381 // int& r = m_field;
382 auto parentsRange = compiler.getASTContext().getParents(*child);
383 if (parentsRange.begin() != parentsRange.end())
385 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
386 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
387 // which is of type 'T&&' and also an l-value-ref ?
388 if (varDecl && !varDecl->isImplicit()
389 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
391 bCannotBeConst = true;
394 break;
396 if (isa<CXXReinterpretCastExpr>(parent))
398 // once we see one of these, there is not much useful we can know
399 bCannotBeConst = true;
400 break;
402 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
403 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
404 || isa<ExprWithCleanups>(parent))
406 walkUp();
408 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
410 UnaryOperator::Opcode op = unaryOperator->getOpcode();
411 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
412 || op == UO_PreDec)
414 bCannotBeConst = true;
416 walkUp();
418 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
420 auto callee = getCallee(operatorCallExpr);
421 if (callee)
423 // if calling a non-const operator on the field
424 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
425 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child
426 && !calleeMethodDecl->isConst())
428 bCannotBeConst = true;
430 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
432 bCannotBeConst = true;
435 else
436 bCannotBeConst = true; // conservative, could improve
437 walkUp();
439 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
441 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
442 if (calleeMethodDecl)
444 // if calling a non-const method on the field
445 const Expr* tmp = dyn_cast<Expr>(child);
446 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
448 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
450 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp
451 && !calleeMethodDecl->isConst())
453 bCannotBeConst = true;
454 break;
456 if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr,
457 CalleeWrapper(calleeMethodDecl)))
458 bCannotBeConst = true;
460 else
461 bCannotBeConst = true; // can happen in templates
462 walkUp();
464 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
466 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr,
467 CalleeWrapper(cxxConstructExpr)))
468 bCannotBeConst = true;
469 walkUp();
471 else if (auto callExpr = dyn_cast<CallExpr>(parent))
473 auto callee = getCallee(callExpr);
474 if (callee)
476 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
477 bCannotBeConst = true;
479 else
480 bCannotBeConst = true; // conservative, could improve
481 walkUp();
483 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
485 BinaryOperator::Opcode op = binaryOp->getOpcode();
486 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
487 || op == BO_RemAssign || op == BO_AddAssign
488 || op == BO_SubAssign || op == BO_ShlAssign
489 || op == BO_ShrAssign || op == BO_AndAssign
490 || op == BO_XorAssign || op == BO_OrAssign;
491 if (assignmentOp)
493 if (binaryOp->getLHS() == child)
494 bCannotBeConst = true;
495 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
496 .LvalueReference()
497 .NonConst())
498 // if the LHS is a non-const reference, we could write to the field later on
499 bCannotBeConst = true;
501 walkUp();
503 else if (isa<ReturnStmt>(parent))
505 if (insideFunctionDecl)
507 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
508 if (tc.LvalueReference().NonConst())
509 bCannotBeConst = true;
511 break;
513 else if (isa<SwitchStmt>(parent) || isa<WhileStmt>(parent) || isa<ForStmt>(parent)
514 || isa<IfStmt>(parent) || isa<DoStmt>(parent) || isa<CXXForRangeStmt>(parent)
515 || isa<DefaultStmt>(parent))
517 break;
519 else
521 walkUp();
523 } while (true);
525 if (bDump)
527 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be? writtenTo=%0",
528 compat::getBeginLoc(memberExpr))
529 << bCannotBeConst << memberExpr->getSourceRange();
530 if (parent)
532 report(DiagnosticsEngine::Note, "parent over here", compat::getBeginLoc(parent))
533 << parent->getSourceRange();
534 parent->dump();
536 memberExpr->dump();
537 fieldDecl->getType()->dump();
540 if (bCannotBeConst)
542 cannotBeConstSet.insert(niceName(fieldDecl));
546 bool ConstFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt* child,
547 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
549 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
550 // if it's an array, passing it by value to a method typically means the
551 // callee takes a pointer and can modify the array
552 if (fieldDecl->getType()->isConstantArrayType())
554 for (unsigned i = 0; i < len; ++i)
555 if (callExpr.getArg(i) == child)
556 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
557 return true;
559 else
561 for (unsigned i = 0; i < len; ++i)
562 if (callExpr.getArg(i) == child)
563 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
564 .LvalueReference()
565 .NonConst())
566 return true;
568 return false;
571 llvm::Optional<CalleeWrapper> ConstFields::getCallee(CallExpr const* callExpr)
573 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
574 if (functionDecl)
575 return CalleeWrapper(functionDecl);
577 // Extract the functionprototype from a type
578 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
579 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
581 if (auto prototype = pointerType->getPointeeType()
582 ->getUnqualifiedDesugaredType()
583 ->getAs<FunctionProtoType>())
585 return CalleeWrapper(prototype);
589 return llvm::Optional<CalleeWrapper>();
592 loplugin::Plugin::Registration<ConstFields> X("constfields", false);
595 #endif
597 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */