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/.
18 Look for fields that are only ever assigned a single constant value.
20 We dmp a list of values assigned to fields, and a list of field definitions.
21 Then we will post-process the 2 lists and find the set of interesting fields.
23 Be warned that it produces around 5G of log file.
25 The process goes something like this:
27 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='singlevalfields' check
28 $ ./compilerplugins/clang/singlevalfields.py
30 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
33 @TODO we don't spot fields that have been zero-initialised via calloc or rtl_allocateZeroMemory or memset
34 @TODO calls to lambdas where a reference to the field is taken
42 FieldDecl
const * fieldDecl
;
43 std::string parentClass
;
44 std::string fieldName
;
45 std::string fieldType
;
46 std::string sourceLocation
;
48 bool operator < (const MyFieldInfo
&lhs
, const MyFieldInfo
&rhs
)
50 return std::tie(lhs
.parentClass
, lhs
.fieldName
)
51 < std::tie(rhs
.parentClass
, rhs
.fieldName
);
54 struct MyFieldAssignmentInfo
: public MyFieldInfo
59 bool operator < (const MyFieldAssignmentInfo
&lhs
, const MyFieldAssignmentInfo
&rhs
)
61 return std::tie(lhs
.parentClass
, lhs
.fieldName
, lhs
.value
)
62 < std::tie(rhs
.parentClass
, rhs
.fieldName
, rhs
.value
);
66 // try to limit the voluminous output a little
67 static std::set
<MyFieldAssignmentInfo
> assignedSet
;
68 static std::set
<MyFieldInfo
> definitionSet
;
71 class SingleValFields
:
72 public RecursiveASTVisitor
<SingleValFields
>, public loplugin::Plugin
75 explicit SingleValFields(loplugin::InstantiationData
const & data
):
78 virtual void run() override
80 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
82 if (!isUnitTestMode())
84 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
85 // writing to the same logfile
87 for (const MyFieldAssignmentInfo
& s
: assignedSet
)
88 output
+= "asgn:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.value
+ "\n";
89 for (const MyFieldInfo
& s
: definitionSet
)
90 output
+= "defn:\t" + s
.parentClass
+ "\t" + s
.fieldName
+ "\t" + s
.fieldType
+ "\t" + s
.sourceLocation
+ "\n";
92 myfile
.open( WORKDIR
"/loplugin.singlevalfields.log", std::ios::app
| std::ios::out
);
98 for (const MyFieldAssignmentInfo
& s
: assignedSet
)
99 if (s
.fieldDecl
&& compiler
.getSourceManager().isInMainFile(compat::getBeginLoc(s
.fieldDecl
)))
101 DiagnosticsEngine::Warning
,
103 compat::getBeginLoc(s
.fieldDecl
))
108 bool shouldVisitTemplateInstantiations () const { return true; }
109 // to catch compiler-generated constructors
110 bool shouldVisitImplicitCode() const { return true; }
112 bool VisitFieldDecl( const FieldDecl
* );
113 bool VisitVarDecl( const VarDecl
* );
114 bool VisitMemberExpr( const MemberExpr
* );
115 bool VisitDeclRefExpr( const DeclRefExpr
* );
116 bool VisitCXXConstructorDecl( const CXXConstructorDecl
* );
117 // bool VisitUnaryExprOrTypeTraitExpr( const UnaryExprOrTypeTraitExpr* );
119 void niceName(const DeclaratorDecl
*, MyFieldInfo
&);
120 void walkPotentialAssign( const DeclaratorDecl
* fieldOrVarDecl
, const Stmt
* stmt
);
121 std::string
getExprValue(const Expr
*);
122 const FunctionDecl
* get_top_FunctionDecl_from_Stmt(const Stmt
&);
123 void checkCallExpr(const Stmt
* child
, const CallExpr
* callExpr
, std::string
& assignValue
, bool& bPotentiallyAssignedTo
);
126 void SingleValFields::niceName(const DeclaratorDecl
* fieldOrVarDecl
, MyFieldInfo
& aInfo
)
128 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(fieldOrVarDecl
);
129 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(fieldOrVarDecl
);
130 aInfo
.fieldDecl
= fieldDecl
;
132 aInfo
.parentClass
= fieldDecl
->getParent()->getQualifiedNameAsString();
135 if (auto parentRecordDecl
= dyn_cast
<CXXRecordDecl
>(varDecl
->getDeclContext()))
136 aInfo
.parentClass
= parentRecordDecl
->getQualifiedNameAsString();
137 else if (auto parentMethodDecl
= dyn_cast
<CXXMethodDecl
>(varDecl
->getDeclContext()))
138 aInfo
.parentClass
= parentMethodDecl
->getQualifiedNameAsString();
139 else if (auto parentFunctionDecl
= dyn_cast
<FunctionDecl
>(varDecl
->getDeclContext()))
140 aInfo
.parentClass
= parentFunctionDecl
->getQualifiedNameAsString();
141 else if (isa
<TranslationUnitDecl
>(varDecl
->getDeclContext()))
142 aInfo
.parentClass
= handler
.getMainFileName();
143 else if (auto parentNamespaceDecl
= dyn_cast
<NamespaceDecl
>(varDecl
->getDeclContext()))
144 aInfo
.parentClass
= parentNamespaceDecl
->getQualifiedNameAsString();
145 else if (isa
<LinkageSpecDecl
>(varDecl
->getDeclContext()))
146 aInfo
.parentClass
= "extern"; // what to do here?
149 std::cout
<< "what is this? " << varDecl
->getDeclContext()->getDeclKindName() << std::endl
;
153 aInfo
.fieldName
= fieldOrVarDecl
->getNameAsString();
154 aInfo
.fieldType
= fieldOrVarDecl
->getType().getAsString();
156 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( fieldOrVarDecl
->getLocation() );
157 StringRef name
= compiler
.getSourceManager().getFilename(expansionLoc
);
158 aInfo
.sourceLocation
= std::string(name
.substr(strlen(SRCDIR
)+1)) + ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
159 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
162 bool SingleValFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
164 auto canonicalDecl
= fieldDecl
->getCanonicalDecl();
166 if( ignoreLocation( canonicalDecl
)
167 || isInUnoIncludeFile( compiler
.getSourceManager().getSpellingLoc(canonicalDecl
->getLocation())) )
171 niceName(canonicalDecl
, aInfo
);
172 definitionSet
.insert(aInfo
);
174 if (fieldDecl
->getInClassInitializer())
176 MyFieldAssignmentInfo aInfo
;
177 niceName(canonicalDecl
, aInfo
);
178 aInfo
.value
= getExprValue(fieldDecl
->getInClassInitializer());
179 assignedSet
.insert(aInfo
);
185 bool SingleValFields::VisitVarDecl( const VarDecl
* varDecl
)
187 if (isa
<ParmVarDecl
>(varDecl
))
189 if (varDecl
->getType().isConstQualified())
191 if (!(varDecl
->isStaticLocal() || varDecl
->isStaticDataMember() || varDecl
->hasGlobalStorage()))
194 auto canonicalDecl
= varDecl
->getCanonicalDecl();
195 if (!canonicalDecl
->getLocation().isValid())
198 if( ignoreLocation( canonicalDecl
)
199 || isInUnoIncludeFile( compiler
.getSourceManager().getSpellingLoc(canonicalDecl
->getLocation())) )
203 niceName(canonicalDecl
, aInfo
);
204 definitionSet
.insert(aInfo
);
206 if (varDecl
->getInit())
208 MyFieldAssignmentInfo aInfo
;
209 niceName(canonicalDecl
, aInfo
);
210 aInfo
.value
= getExprValue(varDecl
->getInit());
211 assignedSet
.insert(aInfo
);
217 bool SingleValFields::VisitCXXConstructorDecl( const CXXConstructorDecl
* decl
)
219 if( ignoreLocation( decl
) )
222 // doesn't count as a write to fields because it's self->self
223 if (decl
->isCopyOrMoveConstructor())
226 for(auto it
= decl
->init_begin(); it
!= decl
->init_end(); ++it
)
228 const CXXCtorInitializer
* init
= *it
;
229 const FieldDecl
* fieldDecl
= init
->getMember();
232 MyFieldAssignmentInfo aInfo
;
233 niceName(fieldDecl
, aInfo
);
234 const Expr
* expr
= init
->getInit();
235 // unwrap any single-arg constructors, this helps to find smart pointers
236 // that are only assigned nullptr
237 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
238 if (cxxConstructExpr
->getNumArgs() == 1)
239 expr
= cxxConstructExpr
->getArg(0);
240 aInfo
.value
= getExprValue(expr
);
241 assignedSet
.insert(aInfo
);
246 bool SingleValFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
248 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
249 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
252 if (ignoreLocation(memberExpr
))
254 walkPotentialAssign(fieldDecl
, memberExpr
);
258 bool SingleValFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
260 const VarDecl
* varDecl
= dyn_cast_or_null
<VarDecl
>(declRefExpr
->getDecl());
263 if (isa
<ParmVarDecl
>(varDecl
))
265 if (varDecl
->getType().isConstQualified())
267 if (!(varDecl
->isStaticLocal() || varDecl
->isStaticDataMember() || varDecl
->hasGlobalStorage()))
269 if (ignoreLocation(declRefExpr
))
271 walkPotentialAssign(varDecl
, declRefExpr
);
275 void SingleValFields::walkPotentialAssign( const DeclaratorDecl
* fieldOrVarDecl
, const Stmt
* memberExpr
)
277 const FunctionDecl
* parentFunction
= getParentFunctionDecl(memberExpr
);
280 auto methodDecl
= dyn_cast
<CXXMethodDecl
>(parentFunction
);
281 if (methodDecl
&& (methodDecl
->isCopyAssignmentOperator() || methodDecl
->isMoveAssignmentOperator()))
283 if (methodDecl
&& methodDecl
->getIdentifier()
284 && (methodDecl
->getName().startswith("Clone") || methodDecl
->getName().startswith("clone")))
286 auto cxxConstructorDecl
= dyn_cast
<CXXConstructorDecl
>(parentFunction
);
287 if (cxxConstructorDecl
&& cxxConstructorDecl
->isCopyOrMoveConstructor())
291 // walk up the tree until we find something interesting
292 const Stmt
* child
= memberExpr
;
293 const Stmt
* parent
= getParentStmt(memberExpr
);
294 bool bPotentiallyAssignedTo
= false;
296 std::string assignValue
= "?";
298 // check for field being returned by non-const ref eg. Foo& getFoo() { return f; }
299 if (parentFunction
&& parent
&& isa
<ReturnStmt
>(parent
)) {
300 const Stmt
* parent2
= getParentStmt(parent
);
301 if (parent2
&& isa
<CompoundStmt
>(parent2
)) {
302 QualType qt
= parentFunction
->getReturnType().getDesugaredType(compiler
.getASTContext());
303 if (!qt
.isConstQualified() && qt
->isReferenceType()) {
304 bPotentiallyAssignedTo
= true;
309 while (!bPotentiallyAssignedTo
) {
310 // check for field being accessed by a reference variable e.g. Foo& f = m.foo;
311 auto parentsList
= compiler
.getASTContext().getParents(*child
);
312 auto it
= parentsList
.begin();
313 if (it
!= parentsList
.end()) {
314 const VarDecl
*varDecl
= it
->get
<VarDecl
>();
316 QualType qt
= varDecl
->getType().getDesugaredType(compiler
.getASTContext());
317 if (!qt
.isConstQualified() && qt
->isReferenceType()) {
318 bPotentiallyAssignedTo
= true;
327 if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
328 || isa
<ExprWithCleanups
>(parent
))
331 parent
= getParentStmt(parent
);
333 else if (isa
<UnaryOperator
>(parent
))
335 const UnaryOperator
* unaryOperator
= dyn_cast
<UnaryOperator
>(parent
);
336 int x
= unaryOperator
->getOpcode();
337 if (x
== UO_AddrOf
|| x
== UO_PostInc
|| x
== UO_PostDec
|| x
== UO_PreInc
|| x
== UO_PreDec
) {
339 bPotentiallyAssignedTo
= true;
342 // cannot be assigned to anymore
345 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
347 checkCallExpr(child
, callExpr
, assignValue
, bPotentiallyAssignedTo
);
350 else if (isa
<CXXConstructExpr
>(parent
))
352 const CXXConstructExpr
* consExpr
= dyn_cast
<CXXConstructExpr
>(parent
);
353 const CXXConstructorDecl
* consDecl
= consExpr
->getConstructor();
354 for (unsigned i
= 0; i
< consExpr
->getNumArgs(); ++i
) {
355 if (i
>= consDecl
->getNumParams()) // can happen in template code
357 if (consExpr
->getArg(i
) == child
) {
358 const ParmVarDecl
* parmVarDecl
= consDecl
->getParamDecl(i
);
359 QualType qt
= parmVarDecl
->getType().getDesugaredType(compiler
.getASTContext());
360 if (!qt
.isConstQualified() && qt
->isReferenceType()) {
361 bPotentiallyAssignedTo
= true;
368 else if (isa
<BinaryOperator
>(parent
))
370 const BinaryOperator
* binaryOp
= dyn_cast
<BinaryOperator
>(parent
);
371 auto op
= binaryOp
->getOpcode();
372 if ( binaryOp
->getLHS() != child
) {
373 // if the expr is on the RHS, do nothing
375 else if ( op
== BO_Assign
) {
376 assignValue
= getExprValue(binaryOp
->getRHS());
377 bPotentiallyAssignedTo
= true;
378 } else if ( op
== BO_MulAssign
|| op
== BO_DivAssign
379 || op
== BO_RemAssign
|| op
== BO_AddAssign
380 || op
== BO_SubAssign
|| op
== BO_ShlAssign
381 || op
== BO_ShrAssign
|| op
== BO_AndAssign
382 || op
== BO_XorAssign
|| op
== BO_OrAssign
)
384 bPotentiallyAssignedTo
= true;
388 else if ( isa
<CompoundStmt
>(parent
)
389 || isa
<SwitchStmt
>(parent
) || isa
<CaseStmt
>(parent
) || isa
<DefaultStmt
>(parent
)
390 || isa
<DoStmt
>(parent
) || isa
<WhileStmt
>(parent
)
391 || isa
<IfStmt
>(parent
)
392 || isa
<ForStmt
>(parent
)
393 || isa
<ReturnStmt
>(parent
)
394 || isa
<CXXNewExpr
>(parent
)
395 || isa
<CXXDeleteExpr
>(parent
)
396 || isa
<ConditionalOperator
>(parent
)
397 || isa
<CXXTypeidExpr
>(parent
)
398 || isa
<ArraySubscriptExpr
>(parent
)
399 || isa
<CXXDependentScopeMemberExpr
>(parent
)
400 || isa
<DeclStmt
>(parent
)
401 || isa
<UnaryExprOrTypeTraitExpr
>(parent
)
402 || isa
<UnresolvedMemberExpr
>(parent
)
403 || isa
<MaterializeTemporaryExpr
>(parent
) //???
404 || isa
<InitListExpr
>(parent
)
405 || isa
<CXXUnresolvedConstructExpr
>(parent
)
406 || isa
<LambdaExpr
>(parent
)
407 || isa
<PackExpansionExpr
>(parent
)
408 || isa
<CXXPseudoDestructorExpr
>(parent
)
413 else if ( isa
<ArrayInitLoopExpr
>(parent
) || isa
<GCCAsmStmt
>(parent
) || isa
<VAArgExpr
>(parent
))
415 bPotentiallyAssignedTo
= true;
419 bPotentiallyAssignedTo
= true;
427 DiagnosticsEngine::Warning
,
428 "oh dear, what can the matter be?",
429 compat::getBeginLoc(memberExpr
))
430 << memberExpr
->getSourceRange();
433 if (bPotentiallyAssignedTo
)
435 MyFieldAssignmentInfo aInfo
;
436 niceName(fieldOrVarDecl
, aInfo
);
437 aInfo
.value
= assignValue
;
438 assignedSet
.insert(aInfo
);
442 void SingleValFields::checkCallExpr(const Stmt
* child
, const CallExpr
* callExpr
, std::string
& assignValue
, bool& bPotentiallyAssignedTo
)
444 if (callExpr
->getCallee() == child
) {
447 const FunctionDecl
* functionDecl
;
448 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(callExpr
)) {
449 functionDecl
= memberCallExpr
->getMethodDecl();
451 functionDecl
= callExpr
->getDirectCallee();
454 if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(callExpr
)) {
455 if (operatorCallExpr
->getArg(0) == child
) {
456 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(operatorCallExpr
->getDirectCallee());
457 if (calleeMethodDecl
) {
458 if (operatorCallExpr
->getOperator() == OO_Equal
) {
459 assignValue
= getExprValue(operatorCallExpr
->getArg(1));
460 bPotentiallyAssignedTo
= true;
466 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
467 if (i
>= functionDecl
->getNumParams()) // can happen in template code
469 if (callExpr
->getArg(i
) == child
) {
470 const ParmVarDecl
* parmVarDecl
= functionDecl
->getParamDecl(i
);
471 QualType qt
= parmVarDecl
->getType().getDesugaredType(compiler
.getASTContext());
472 if (!qt
.isConstQualified() && qt
->isReferenceType()) {
474 bPotentiallyAssignedTo
= true;
481 // check for function pointers
482 const FieldDecl
* calleeFieldDecl
= dyn_cast_or_null
<FieldDecl
>(callExpr
->getCalleeDecl());
483 if (!calleeFieldDecl
) {
486 QualType qt
= calleeFieldDecl
->getType().getDesugaredType(compiler
.getASTContext());
487 if (!qt
->isPointerType()) {
490 qt
= qt
->getPointeeType().getDesugaredType(compiler
.getASTContext());
491 const FunctionProtoType
* proto
= qt
->getAs
<FunctionProtoType
>();
495 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
496 if (i
>= proto
->getNumParams()) // can happen in template code
498 if (callExpr
->getArg(i
) == child
) {
499 QualType qt
= proto
->getParamType(i
).getDesugaredType(compiler
.getASTContext());
500 if (!qt
.isConstQualified() && qt
->isReferenceType()) {
502 bPotentiallyAssignedTo
= true;
510 std::string
SingleValFields::getExprValue(const Expr
* arg
)
514 arg
= arg
->IgnoreParenCasts();
515 arg
= arg
->IgnoreImplicit();
516 // ignore this, it seems to trigger an infinite recursion
517 if (isa
<UnaryExprOrTypeTraitExpr
>(arg
))
519 if (arg
->isValueDependent())
521 // for stuff like: OUString foo = "xxx";
522 if (auto stringLiteral
= dyn_cast
<clang::StringLiteral
>(arg
))
524 if (stringLiteral
->getCharByteWidth() == 1)
525 return stringLiteral
->getString();
528 // ParenListExpr containing a CXXNullPtrLiteralExpr and has a NULL type pointer
529 if (auto parenListExpr
= dyn_cast
<ParenListExpr
>(arg
))
531 if (parenListExpr
->getNumExprs() == 1)
532 return getExprValue(parenListExpr
->getExpr(0));
535 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(arg
))
537 if (constructExpr
->getNumArgs() >= 1
538 && isa
<clang::StringLiteral
>(constructExpr
->getArg(0)))
540 auto stringLiteral
= dyn_cast
<clang::StringLiteral
>(constructExpr
->getArg(0));
541 if (stringLiteral
->getCharByteWidth() == 1)
542 return stringLiteral
->getString();
546 if (arg
->getType()->isFloatingType())
549 if (arg
->EvaluateAsFloat(x1
, compiler
.getASTContext()))
552 llvm::raw_string_ostream
os(s
);
558 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
559 return x1
.toString(10);
560 if (isa
<CXXNullPtrLiteralExpr
>(arg
))
565 loplugin::Plugin::Registration
< SingleValFields
> X("singlevalfields", false);
569 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */