1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
16 #include <unordered_set>
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:
30 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='constfields' check
31 $ ./compilerplugins/clang/constfields.py
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
43 const RecordDecl
* parentRecord
;
44 std::string parentClass
;
45 std::string fieldName
;
46 std::string fieldType
;
47 std::string sourceLocation
;
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
;
60 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
64 const CallExpr
* m_callExpr
;
65 const CXXConstructExpr
* m_cxxConstructExpr
;
68 CallerWrapper(const CallExpr
* callExpr
)
69 : m_callExpr(callExpr
)
70 , m_cxxConstructExpr(nullptr)
73 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
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
);
89 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
90 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
91 const FunctionProtoType
* m_functionPrototype
= nullptr;
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
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();
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();
136 CXXMethodDecl
const* getAsCXXMethodDecl() const
138 if (m_calleeFunctionDecl
)
139 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
144 class ConstFields
: public RecursiveASTVisitor
<ConstFields
>, public loplugin::Plugin
147 explicit ConstFields(loplugin::InstantiationData
const& 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
*);
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
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
);
200 for (const MyFieldInfo
& s
: cannotBeConstSet
)
201 report(DiagnosticsEngine::Warning
, "notconst %0", compat::getBeginLoc(s
.parentRecord
))
206 MyFieldInfo
ConstFields::niceName(const FieldDecl
* fieldDecl
)
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();
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
);
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())
245 aInfo
.access
= "public";
248 aInfo
.access
= "private";
251 aInfo
.access
= "protected";
254 aInfo
.access
= "unknown";
261 bool ConstFields::VisitFieldDecl(const FieldDecl
* fieldDecl
)
263 fieldDecl
= fieldDecl
->getCanonicalDecl();
264 if (ignoreLocation(fieldDecl
))
268 // ignore stuff that forms part of the stable URE interface
269 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
273 definitionSet
.insert(niceName(fieldDecl
));
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
;
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
;
306 bool ConstFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
308 auto copy2
= insideFunctionDecl
;
309 insideFunctionDecl
= functionDecl
;
310 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
311 insideFunctionDecl
= copy2
;
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
);
326 insideConditionalCheckOfMemberSet
.pop_back();
330 bool ConstFields::VisitMemberExpr(const MemberExpr
* memberExpr
)
332 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
333 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
338 fieldDecl
= fieldDecl
->getCanonicalDecl();
339 if (ignoreLocation(fieldDecl
))
343 // ignore stuff that forms part of the stable URE interface
344 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
349 check(fieldDecl
, memberExpr
);
354 void ConstFields::check(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
356 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
357 const Stmt
* child
= memberExpr
;
359 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
360 // walk up the tree until we find something interesting
361 bool bCannotBeConst
= false;
363 auto walkUp
= [&]() {
365 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
366 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
367 : parentsRange
.begin()->get
<Stmt
>();
373 // check if we have an expression like
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;
389 if (isa
<CXXReinterpretCastExpr
>(parent
))
391 // once we see one of these, there is not much useful we can know
392 bCannotBeConst
= true;
395 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
396 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
397 || isa
<ExprWithCleanups
>(parent
))
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
407 bCannotBeConst
= true;
411 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
413 auto callee
= getCallee(operatorCallExpr
);
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;
429 bCannotBeConst
= true; // conservative, could improve
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;
449 if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
,
450 CalleeWrapper(calleeMethodDecl
)))
451 bCannotBeConst
= true;
454 bCannotBeConst
= true; // can happen in templates
457 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
459 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
,
460 CalleeWrapper(cxxConstructExpr
)))
461 bCannotBeConst
= true;
464 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
466 auto callee
= getCallee(callExpr
);
469 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
470 bCannotBeConst
= true;
473 bCannotBeConst
= true; // conservative, could improve
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
;
486 if (binaryOp
->getLHS() == child
)
487 bCannotBeConst
= true;
488 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
491 // if the LHS is a non-const reference, we could write to the field later on
492 bCannotBeConst
= true;
496 else if (isa
<ReturnStmt
>(parent
))
498 if (insideFunctionDecl
)
500 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
501 if (tc
.LvalueReference().NonConst())
502 bCannotBeConst
= true;
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
))
520 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
521 compat::getBeginLoc(memberExpr
))
522 << bCannotBeConst
<< memberExpr
->getSourceRange();
525 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
526 << parent
->getSourceRange();
530 fieldDecl
->getType()->dump();
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())
554 for (unsigned i
= 0; i
< len
; ++i
)
555 if (callExpr
.getArg(i
) == child
)
556 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
564 llvm::Optional
<CalleeWrapper
> ConstFields::getCallee(CallExpr
const* callExpr
)
566 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
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);
590 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */