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>
22 #include "config_clang.h"
27 #include "clang/AST/ParentMapContext.h"
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:
34 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='constfields' check
35 $ ./compilerplugins/clang/constfields.py
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
47 const RecordDecl
* parentRecord
;
48 std::string parentClass
;
49 std::string fieldName
;
50 std::string fieldType
;
51 std::string sourceLocation
;
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
;
64 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
68 const CallExpr
* m_callExpr
;
69 const CXXConstructExpr
* m_cxxConstructExpr
;
72 CallerWrapper(const CallExpr
* callExpr
)
73 : m_callExpr(callExpr
)
74 , m_cxxConstructExpr(nullptr)
77 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
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
);
93 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
94 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
95 const FunctionProtoType
* m_functionPrototype
= nullptr;
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
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();
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();
140 CXXMethodDecl
const* getAsCXXMethodDecl() const
142 if (m_calleeFunctionDecl
)
143 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
148 class ConstFields
: public RecursiveASTVisitor
<ConstFields
>, public loplugin::Plugin
151 explicit ConstFields(loplugin::InstantiationData
const& 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
*);
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
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
);
204 for (const MyFieldInfo
& s
: cannotBeConstSet
)
205 report(DiagnosticsEngine::Warning
, "notconst %0", s
.parentRecord
->getBeginLoc())
210 MyFieldInfo
ConstFields::niceName(const FieldDecl
* fieldDecl
)
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();
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
);
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())
249 aInfo
.access
= "public";
252 aInfo
.access
= "private";
255 aInfo
.access
= "protected";
258 aInfo
.access
= "unknown";
265 bool ConstFields::VisitFieldDecl(const FieldDecl
* fieldDecl
)
267 fieldDecl
= fieldDecl
->getCanonicalDecl();
268 if (ignoreLocation(fieldDecl
))
272 // ignore stuff that forms part of the stable URE interface
273 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
277 definitionSet
.insert(niceName(fieldDecl
));
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
;
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
;
310 bool ConstFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
312 auto copy2
= insideFunctionDecl
;
313 insideFunctionDecl
= functionDecl
;
314 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
315 insideFunctionDecl
= copy2
;
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
);
332 insideConditionalCheckOfMemberSet
.pop_back();
336 bool ConstFields::VisitMemberExpr(const MemberExpr
* memberExpr
)
338 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
339 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
344 fieldDecl
= fieldDecl
->getCanonicalDecl();
345 if (ignoreLocation(fieldDecl
))
349 // ignore stuff that forms part of the stable URE interface
350 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
355 check(fieldDecl
, memberExpr
);
360 void ConstFields::check(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
362 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
363 const Stmt
* child
= memberExpr
;
365 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
366 // walk up the tree until we find something interesting
367 bool bCannotBeConst
= false;
369 auto walkUp
= [&]() {
371 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
372 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
373 : parentsRange
.begin()->get
<Stmt
>();
379 // check if we have an expression like
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;
395 if (isa
<CXXReinterpretCastExpr
>(parent
))
397 // once we see one of these, there is not much useful we can know
398 bCannotBeConst
= true;
401 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
402 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
403 || isa
<ExprWithCleanups
>(parent
))
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
413 bCannotBeConst
= true;
417 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
419 auto callee
= getCallee(operatorCallExpr
);
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;
435 bCannotBeConst
= true; // conservative, could improve
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;
455 if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
,
456 CalleeWrapper(calleeMethodDecl
)))
457 bCannotBeConst
= true;
460 bCannotBeConst
= true; // can happen in templates
463 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
465 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
,
466 CalleeWrapper(cxxConstructExpr
)))
467 bCannotBeConst
= true;
470 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
472 auto callee
= getCallee(callExpr
);
475 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
476 bCannotBeConst
= true;
479 bCannotBeConst
= true; // conservative, could improve
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
;
492 if (binaryOp
->getLHS() == child
)
493 bCannotBeConst
= true;
494 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
497 // if the LHS is a non-const reference, we could write to the field later on
498 bCannotBeConst
= true;
502 else if (isa
<ReturnStmt
>(parent
))
504 if (insideFunctionDecl
)
506 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
507 if (tc
.LvalueReference().NonConst())
508 bCannotBeConst
= true;
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
))
526 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
527 memberExpr
->getBeginLoc())
528 << bCannotBeConst
<< memberExpr
->getSourceRange();
531 report(DiagnosticsEngine::Note
, "parent over here", parent
->getBeginLoc())
532 << parent
->getSourceRange();
536 fieldDecl
->getType()->dump();
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())
560 for (unsigned i
= 0; i
< len
; ++i
)
561 if (callExpr
.getArg(i
) == child
)
562 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
570 llvm::Optional
<CalleeWrapper
> ConstFields::getCallee(CallExpr
const* callExpr
)
572 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
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);
596 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */