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 #include "clang/AST/ParentMapContext.h"
31 Look for fields on objects that can be local variables.
32 Not a particularly smart plugin, generates a lot of false positives, and requires review of the output.
33 Mostly looks for fields that are only accessed within a single method.
40 std::string returnType
;
41 std::string nameAndParams
;
42 std::string sourceLocation
;
47 std::string parentClass
;
48 std::string fieldName
;
49 std::string fieldType
;
50 std::string sourceLocation
;
53 // try to limit the voluminous output a little
54 // if the value is nullptr, that indicates that we touched that field from more than one function
55 static std::unordered_map
<const FieldDecl
*, const FunctionDecl
*> touchedMap
;
57 class FieldCanBeLocal
: public loplugin::FilteringPlugin
<FieldCanBeLocal
>
60 explicit FieldCanBeLocal(loplugin::InstantiationData
const& data
)
61 : FilteringPlugin(data
)
65 virtual void run() override
;
67 bool shouldVisitTemplateInstantiations() const { return true; }
68 bool shouldVisitImplicitCode() const { return true; }
70 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
71 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
72 bool TraverseFunctionDecl(FunctionDecl
*);
74 bool VisitMemberExpr(const MemberExpr
*);
75 bool VisitDeclRefExpr(const DeclRefExpr
*);
76 bool VisitInitListExpr(const InitListExpr
*);
77 bool VisitCXXConstructorDecl(const CXXConstructorDecl
*);
80 MyFieldInfo
niceName(const FieldDecl
*);
81 MyFuncInfo
niceName(const FunctionDecl
*);
82 std::string
toString(SourceLocation loc
);
83 void checkTouched(const FieldDecl
* fieldDecl
, const FunctionDecl
*);
84 bool isSomeKindOfConstant(const Expr
* arg
);
86 RecordDecl
* insideMoveOrCopyOrCloneDeclParent
= nullptr;
87 RecordDecl
* insideStreamOutputOperator
= nullptr;
88 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
89 // we store the parent function on the way down the AST.
90 FunctionDecl
* insideFunctionDecl
= nullptr;
93 void FieldCanBeLocal::run()
95 handler
.enableTreeWideAnalysisMode();
97 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
99 if (!isUnitTestMode())
101 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
102 // writing to the same logfile
104 output
.reserve(64 * 1024);
105 for (const auto& pair
: touchedMap
)
107 if (pair
.first
->getParent()->isLambda())
109 MyFieldInfo s
= niceName(pair
.first
);
110 output
+= "definition:\t" + s
.parentClass
//
111 + "\t" + s
.fieldName
//
112 + "\t" + s
.fieldType
//
113 + "\t" + s
.sourceLocation
//
115 // we have to output a negative, in case, in some other file, it is touched only once
117 output
+= "touched:\t" + s
.parentClass
//
118 + "\t" + s
.fieldName
//
124 MyFuncInfo s2
= niceName(pair
.second
);
125 output
+= "touched:\t" + s
.parentClass
//
126 + "\t" + s
.fieldName
//
127 + "\t" + s2
.returnType
+ " " + s2
.nameAndParams
//
128 + "\t" + s2
.sourceLocation
//
132 std::ofstream myfile
;
133 myfile
.open(WORKDIR
"/loplugin.fieldcanbelocal.log", std::ios::app
| std::ios::out
);
139 // for (const MyFieldInfo & s : readFromSet)
141 // DiagnosticsEngine::Warning,
143 // s.parentRecord->getBeginLoc())
148 MyFieldInfo
FieldCanBeLocal::niceName(const FieldDecl
* fieldDecl
)
152 const RecordDecl
* recordDecl
= fieldDecl
->getParent();
154 if (const CXXRecordDecl
* cxxRecordDecl
= dyn_cast
<CXXRecordDecl
>(recordDecl
))
156 if (cxxRecordDecl
->getTemplateInstantiationPattern())
157 cxxRecordDecl
= cxxRecordDecl
->getTemplateInstantiationPattern();
158 aInfo
.parentClass
= cxxRecordDecl
->getQualifiedNameAsString();
162 aInfo
.parentClass
= recordDecl
->getQualifiedNameAsString();
165 aInfo
.fieldName
= fieldDecl
->getNameAsString();
166 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
167 size_t idx
= aInfo
.fieldName
.find(SRCDIR
);
168 if (idx
!= std::string::npos
)
170 aInfo
.fieldName
= aInfo
.fieldName
.replace(idx
, strlen(SRCDIR
), "");
172 aInfo
.fieldType
= fieldDecl
->getType().getAsString();
174 SourceLocation expansionLoc
175 = compiler
.getSourceManager().getExpansionLoc(fieldDecl
->getLocation());
176 StringRef name
= getFilenameOfLocation(expansionLoc
);
178 = std::string(name
.substr(strlen(SRCDIR
) + 1)) + ":"
179 + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
180 loplugin::normalizeDotDotInFilePath(aInfo
.sourceLocation
);
185 MyFuncInfo
FieldCanBeLocal::niceName(const FunctionDecl
* functionDecl
)
187 if (functionDecl
->getInstantiatedFromMemberFunction())
188 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
189 else if (functionDecl
->getTemplateInstantiationPattern())
190 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
193 if (!isa
<CXXConstructorDecl
>(functionDecl
))
195 aInfo
.returnType
= functionDecl
->getReturnType().getCanonicalType().getAsString();
199 aInfo
.returnType
= "";
202 if (isa
<CXXMethodDecl
>(functionDecl
))
204 const CXXRecordDecl
* recordDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)->getParent();
205 aInfo
.nameAndParams
+= recordDecl
->getQualifiedNameAsString();
206 aInfo
.nameAndParams
+= "::";
208 aInfo
.nameAndParams
+= functionDecl
->getNameAsString() + "(";
210 for (const ParmVarDecl
* pParmVarDecl
: functionDecl
->parameters())
215 aInfo
.nameAndParams
+= ",";
216 aInfo
.nameAndParams
+= pParmVarDecl
->getType().getCanonicalType().getAsString();
218 aInfo
.nameAndParams
+= ")";
219 if (isa
<CXXMethodDecl
>(functionDecl
) && dyn_cast
<CXXMethodDecl
>(functionDecl
)->isConst())
221 aInfo
.nameAndParams
+= " const";
224 aInfo
.sourceLocation
= toString(functionDecl
->getLocation());
229 std::string
FieldCanBeLocal::toString(SourceLocation loc
)
231 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc(loc
);
232 StringRef name
= getFilenameOfLocation(expansionLoc
);
233 std::string sourceLocation
234 = std::string(name
.substr(strlen(SRCDIR
) + 1)) + ":"
235 + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
236 loplugin::normalizeDotDotInFilePath(sourceLocation
);
237 return sourceLocation
;
240 bool FieldCanBeLocal::TraverseCXXConstructorDecl(CXXConstructorDecl
* cxxConstructorDecl
)
242 auto copy
= insideMoveOrCopyOrCloneDeclParent
;
243 if (!ignoreLocation(cxxConstructorDecl
->getBeginLoc())
244 && cxxConstructorDecl
->isThisDeclarationADefinition())
246 if (cxxConstructorDecl
->isCopyOrMoveConstructor())
247 insideMoveOrCopyOrCloneDeclParent
= cxxConstructorDecl
->getParent();
249 bool ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl
);
250 insideMoveOrCopyOrCloneDeclParent
= copy
;
254 bool FieldCanBeLocal::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
256 auto copy1
= insideMoveOrCopyOrCloneDeclParent
;
257 auto copy2
= insideFunctionDecl
;
258 if (!ignoreLocation(cxxMethodDecl
->getBeginLoc())
259 && cxxMethodDecl
->isThisDeclarationADefinition())
261 if (cxxMethodDecl
->isCopyAssignmentOperator() || cxxMethodDecl
->isMoveAssignmentOperator()
262 || (cxxMethodDecl
->getIdentifier()
263 && (compat::starts_with(cxxMethodDecl
->getName(), "Clone")
264 || compat::starts_with(cxxMethodDecl
->getName(), "clone")
265 || compat::starts_with(cxxMethodDecl
->getName(), "createClone"))))
266 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
267 // these are similar in that they tend to simply enumerate all the fields of an object without putting
268 // them to some useful purpose
269 auto op
= cxxMethodDecl
->getOverloadedOperator();
270 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
271 insideMoveOrCopyOrCloneDeclParent
= cxxMethodDecl
->getParent();
273 insideFunctionDecl
= cxxMethodDecl
;
274 bool ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl
);
275 insideMoveOrCopyOrCloneDeclParent
= copy1
;
276 insideFunctionDecl
= copy2
;
280 bool FieldCanBeLocal::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
282 auto copy1
= insideStreamOutputOperator
;
283 auto copy2
= insideFunctionDecl
;
284 auto copy3
= insideMoveOrCopyOrCloneDeclParent
;
285 if (functionDecl
->getLocation().isValid() && !ignoreLocation(functionDecl
->getBeginLoc())
286 && functionDecl
->isThisDeclarationADefinition())
288 auto op
= functionDecl
->getOverloadedOperator();
289 if (op
== OO_LessLess
&& functionDecl
->getNumParams() == 2)
291 QualType qt
= functionDecl
->getParamDecl(1)->getType();
292 insideStreamOutputOperator
293 = qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
295 // these are similar in that they tend to simply enumerate all the fields of an object without putting
296 // them to some useful purpose
297 if (op
== OO_EqualEqual
|| op
== OO_ExclaimEqual
)
299 QualType qt
= functionDecl
->getParamDecl(1)->getType();
300 insideMoveOrCopyOrCloneDeclParent
301 = qt
.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
304 insideFunctionDecl
= functionDecl
;
305 bool ret
= RecursiveASTVisitor::TraverseFunctionDecl(functionDecl
);
306 insideStreamOutputOperator
= copy1
;
307 insideFunctionDecl
= copy2
;
308 insideMoveOrCopyOrCloneDeclParent
= copy3
;
312 bool FieldCanBeLocal::VisitMemberExpr(const MemberExpr
* memberExpr
)
314 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
315 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
320 fieldDecl
= fieldDecl
->getCanonicalDecl();
321 if (ignoreLocation(fieldDecl
->getBeginLoc()))
325 // ignore stuff that forms part of the stable URE interface
326 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
331 if (insideMoveOrCopyOrCloneDeclParent
|| insideStreamOutputOperator
)
333 RecordDecl
const* cxxRecordDecl1
= fieldDecl
->getParent();
334 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
335 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideMoveOrCopyOrCloneDeclParent
))
337 // we don't care about reads when the field is being used in an output operator, this is normally
339 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideStreamOutputOperator
))
343 checkTouched(fieldDecl
, insideFunctionDecl
);
348 bool FieldCanBeLocal::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
350 const Decl
* decl
= declRefExpr
->getDecl();
351 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
356 fieldDecl
= fieldDecl
->getCanonicalDecl();
357 if (ignoreLocation(fieldDecl
->getBeginLoc()))
361 // ignore stuff that forms part of the stable URE interface
362 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation())))
367 checkTouched(fieldDecl
, insideFunctionDecl
);
372 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
373 // have to do it here
374 bool FieldCanBeLocal::VisitCXXConstructorDecl(const CXXConstructorDecl
* cxxConstructorDecl
)
376 if (ignoreLocation(cxxConstructorDecl
->getBeginLoc()))
380 // ignore stuff that forms part of the stable URE interface
381 if (isInUnoIncludeFile(
382 compiler
.getSourceManager().getSpellingLoc(cxxConstructorDecl
->getLocation())))
387 // templates make EvaluateAsInt crash inside clang
388 if (cxxConstructorDecl
->isDependentContext())
391 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
392 if (insideMoveOrCopyOrCloneDeclParent
393 && cxxConstructorDecl
->getParent() == insideMoveOrCopyOrCloneDeclParent
)
396 for (auto it
= cxxConstructorDecl
->init_begin(); it
!= cxxConstructorDecl
->init_end(); ++it
)
398 const CXXCtorInitializer
* init
= *it
;
399 const FieldDecl
* fieldDecl
= init
->getMember();
402 if (init
->getInit() && isSomeKindOfConstant(init
->getInit()))
403 checkTouched(fieldDecl
, cxxConstructorDecl
);
405 touchedMap
[fieldDecl
] = nullptr;
410 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
411 // have to do it here.
412 bool FieldCanBeLocal::VisitInitListExpr(const InitListExpr
* initListExpr
)
414 if (ignoreLocation(initListExpr
->getBeginLoc()))
417 QualType varType
= initListExpr
->getType().getDesugaredType(compiler
.getASTContext());
418 auto recordType
= varType
->getAs
<RecordType
>();
422 auto recordDecl
= recordType
->getDecl();
423 for (auto it
= recordDecl
->field_begin(); it
!= recordDecl
->field_end(); ++it
)
425 checkTouched(*it
, insideFunctionDecl
);
431 void FieldCanBeLocal::checkTouched(const FieldDecl
* fieldDecl
, const FunctionDecl
* functionDecl
)
433 auto methodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(functionDecl
);
436 touchedMap
[fieldDecl
] = nullptr;
439 if (methodDecl
->getParent() != fieldDecl
->getParent())
441 touchedMap
[fieldDecl
] = nullptr;
444 auto it
= touchedMap
.find(fieldDecl
);
445 if (it
== touchedMap
.end())
446 touchedMap
.emplace(fieldDecl
, functionDecl
);
447 else if (it
->second
!= functionDecl
)
448 it
->second
= nullptr;
451 bool FieldCanBeLocal::isSomeKindOfConstant(const Expr
* arg
)
454 if (arg
->isValueDependent())
456 return arg
->isCXX11ConstantExpr(compiler
.getASTContext());
459 loplugin::Plugin::Registration
<FieldCanBeLocal
> X("fieldcanbelocal", false);
464 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */