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"
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
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:
37 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='constfields' check
38 $ ./compilerplugins/clang/constfields.py
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
50 const RecordDecl
* parentRecord
;
51 std::string parentClass
;
52 std::string fieldName
;
53 std::string fieldType
;
54 std::string sourceLocation
;
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
;
67 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
71 const CallExpr
* m_callExpr
;
72 const CXXConstructExpr
* m_cxxConstructExpr
;
75 CallerWrapper(const CallExpr
* callExpr
)
76 : m_callExpr(callExpr
)
77 , m_cxxConstructExpr(nullptr)
80 CallerWrapper(const CXXConstructExpr
* cxxConstructExpr
)
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
);
96 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
97 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
98 const FunctionProtoType
* m_functionPrototype
= nullptr;
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
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();
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();
143 CXXMethodDecl
const* getAsCXXMethodDecl() const
145 if (m_calleeFunctionDecl
)
146 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
151 class ConstFields
: public RecursiveASTVisitor
<ConstFields
>, public loplugin::Plugin
154 explicit ConstFields(loplugin::InstantiationData
const& 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
*);
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
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
);
207 for (const MyFieldInfo
& s
: cannotBeConstSet
)
208 report(DiagnosticsEngine::Warning
, "notconst %0", compat::getBeginLoc(s
.parentRecord
))
213 MyFieldInfo
ConstFields::niceName(const FieldDecl
* fieldDecl
)
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();
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
);
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())
252 aInfo
.access
= "public";
255 aInfo
.access
= "private";
258 aInfo
.access
= "protected";
261 aInfo
.access
= "unknown";
268 bool ConstFields::VisitFieldDecl(const FieldDecl
* fieldDecl
)
270 fieldDecl
= fieldDecl
->getCanonicalDecl();
271 if (ignoreLocation(fieldDecl
))
275 // ignore stuff that forms part of the stable URE interface
276 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
280 definitionSet
.insert(niceName(fieldDecl
));
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
;
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
;
313 bool ConstFields::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
315 auto copy2
= insideFunctionDecl
;
316 insideFunctionDecl
= functionDecl
;
317 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
318 insideFunctionDecl
= copy2
;
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
);
333 insideConditionalCheckOfMemberSet
.pop_back();
337 bool ConstFields::VisitMemberExpr(const MemberExpr
* memberExpr
)
339 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
340 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
345 fieldDecl
= fieldDecl
->getCanonicalDecl();
346 if (ignoreLocation(fieldDecl
))
350 // ignore stuff that forms part of the stable URE interface
351 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
356 check(fieldDecl
, memberExpr
);
361 void ConstFields::check(const FieldDecl
* fieldDecl
, const Expr
* memberExpr
)
363 auto parentsRange
= compiler
.getASTContext().getParents(*memberExpr
);
364 const Stmt
* child
= memberExpr
;
366 = parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
367 // walk up the tree until we find something interesting
368 bool bCannotBeConst
= false;
370 auto walkUp
= [&]() {
372 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
373 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr
374 : parentsRange
.begin()->get
<Stmt
>();
380 // check if we have an expression like
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;
396 if (isa
<CXXReinterpretCastExpr
>(parent
))
398 // once we see one of these, there is not much useful we can know
399 bCannotBeConst
= true;
402 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
)
403 || isa
<ParenListExpr
>(parent
) || isa
<ArrayInitLoopExpr
>(parent
)
404 || isa
<ExprWithCleanups
>(parent
))
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
414 bCannotBeConst
= true;
418 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
420 auto callee
= getCallee(operatorCallExpr
);
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;
436 bCannotBeConst
= true; // conservative, could improve
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;
456 if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
,
457 CalleeWrapper(calleeMethodDecl
)))
458 bCannotBeConst
= true;
461 bCannotBeConst
= true; // can happen in templates
464 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
466 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
,
467 CalleeWrapper(cxxConstructExpr
)))
468 bCannotBeConst
= true;
471 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
473 auto callee
= getCallee(callExpr
);
476 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
477 bCannotBeConst
= true;
480 bCannotBeConst
= true; // conservative, could improve
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
;
493 if (binaryOp
->getLHS() == child
)
494 bCannotBeConst
= true;
495 else if (loplugin::TypeCheck(binaryOp
->getLHS()->getType())
498 // if the LHS is a non-const reference, we could write to the field later on
499 bCannotBeConst
= true;
503 else if (isa
<ReturnStmt
>(parent
))
505 if (insideFunctionDecl
)
507 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
508 if (tc
.LvalueReference().NonConst())
509 bCannotBeConst
= true;
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
))
527 report(DiagnosticsEngine::Warning
, "oh dear, what can the matter be? writtenTo=%0",
528 compat::getBeginLoc(memberExpr
))
529 << bCannotBeConst
<< memberExpr
->getSourceRange();
532 report(DiagnosticsEngine::Note
, "parent over here", compat::getBeginLoc(parent
))
533 << parent
->getSourceRange();
537 fieldDecl
->getType()->dump();
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())
561 for (unsigned i
= 0; i
< len
; ++i
)
562 if (callExpr
.getArg(i
) == child
)
563 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
))
571 llvm::Optional
<CalleeWrapper
> ConstFields::getCallee(CallExpr
const* callExpr
)
573 FunctionDecl
const* functionDecl
= callExpr
->getDirectCallee();
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);
597 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */