Remove exec bits from docx
[LibreOffice.git] / compilerplugins / clang / fieldcanbelocal.cxx
blob9d4b60fd094b998b203ebef06f140bafd357b472
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <unordered_set>
17 #include <vector>
18 #include <algorithm>
19 #include <sys/file.h>
20 #include <unistd.h>
22 #include "config_clang.h"
24 #include "plugin.hxx"
25 #include "compat.hxx"
26 #include "check.hxx"
28 #include "clang/AST/ParentMapContext.h"
30 /**
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.
36 namespace
38 struct MyFuncInfo
40 std::string returnType;
41 std::string nameAndParams;
42 std::string sourceLocation;
45 struct MyFieldInfo
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>
59 public:
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*);
79 private:
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
103 std::string output;
104 output.reserve(64 * 1024);
105 for (const auto& pair : touchedMap)
107 if (pair.first->getParent()->isLambda())
108 continue;
109 MyFieldInfo s = niceName(pair.first);
110 output += "definition:\t" + s.parentClass //
111 + "\t" + s.fieldName //
112 + "\t" + s.fieldType //
113 + "\t" + s.sourceLocation //
114 + "\n";
115 // we have to output a negative, in case, in some other file, it is touched only once
116 if (!pair.second)
117 output += "touched:\t" + s.parentClass //
118 + "\t" + s.fieldName //
119 + "\tNegative" //
120 + "\tnowhere.cxx" //
121 + "\n";
122 else
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 //
129 + "\n";
132 std::ofstream myfile;
133 myfile.open(WORKDIR "/loplugin.fieldcanbelocal.log", std::ios::app | std::ios::out);
134 myfile << output;
135 myfile.close();
137 else
139 // for (const MyFieldInfo & s : readFromSet)
140 // report(
141 // DiagnosticsEngine::Warning,
142 // "read %0",
143 // s.parentRecord->getBeginLoc())
144 // << s.fieldName;
148 MyFieldInfo FieldCanBeLocal::niceName(const FieldDecl* fieldDecl)
150 MyFieldInfo aInfo;
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();
160 else
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);
177 aInfo.sourceLocation
178 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
179 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
180 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
182 return aInfo;
185 MyFuncInfo FieldCanBeLocal::niceName(const FunctionDecl* functionDecl)
187 if (functionDecl->getInstantiatedFromMemberFunction())
188 functionDecl = functionDecl->getInstantiatedFromMemberFunction();
189 else if (functionDecl->getTemplateInstantiationPattern())
190 functionDecl = functionDecl->getTemplateInstantiationPattern();
192 MyFuncInfo aInfo;
193 if (!isa<CXXConstructorDecl>(functionDecl))
195 aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
197 else
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() + "(";
209 bool bFirst = true;
210 for (const ParmVarDecl* pParmVarDecl : functionDecl->parameters())
212 if (bFirst)
213 bFirst = false;
214 else
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());
226 return aInfo;
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;
251 return ret;
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;
277 return ret;
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;
309 return ret;
312 bool FieldCanBeLocal::VisitMemberExpr(const MemberExpr* memberExpr)
314 const ValueDecl* decl = memberExpr->getMemberDecl();
315 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
316 if (!fieldDecl)
318 return true;
320 fieldDecl = fieldDecl->getCanonicalDecl();
321 if (ignoreLocation(fieldDecl->getBeginLoc()))
323 return true;
325 // ignore stuff that forms part of the stable URE interface
326 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
328 return true;
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))
336 return true;
337 // we don't care about reads when the field is being used in an output operator, this is normally
338 // debug stuff
339 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator))
340 return true;
343 checkTouched(fieldDecl, insideFunctionDecl);
345 return true;
348 bool FieldCanBeLocal::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
350 const Decl* decl = declRefExpr->getDecl();
351 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
352 if (!fieldDecl)
354 return true;
356 fieldDecl = fieldDecl->getCanonicalDecl();
357 if (ignoreLocation(fieldDecl->getBeginLoc()))
359 return true;
361 // ignore stuff that forms part of the stable URE interface
362 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation())))
364 return true;
367 checkTouched(fieldDecl, insideFunctionDecl);
369 return true;
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()))
378 return true;
380 // ignore stuff that forms part of the stable URE interface
381 if (isInUnoIncludeFile(
382 compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation())))
384 return true;
387 // templates make EvaluateAsInt crash inside clang
388 if (cxxConstructorDecl->isDependentContext())
389 return true;
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)
394 return true;
396 for (auto it = cxxConstructorDecl->init_begin(); it != cxxConstructorDecl->init_end(); ++it)
398 const CXXCtorInitializer* init = *it;
399 const FieldDecl* fieldDecl = init->getMember();
400 if (!fieldDecl)
401 continue;
402 if (init->getInit() && isSomeKindOfConstant(init->getInit()))
403 checkTouched(fieldDecl, cxxConstructorDecl);
404 else
405 touchedMap[fieldDecl] = nullptr;
407 return true;
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()))
415 return true;
417 QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext());
418 auto recordType = varType->getAs<RecordType>();
419 if (!recordType)
420 return true;
422 auto recordDecl = recordType->getDecl();
423 for (auto it = recordDecl->field_begin(); it != recordDecl->field_end(); ++it)
425 checkTouched(*it, insideFunctionDecl);
428 return true;
431 void FieldCanBeLocal::checkTouched(const FieldDecl* fieldDecl, const FunctionDecl* functionDecl)
433 auto methodDecl = dyn_cast_or_null<CXXMethodDecl>(functionDecl);
434 if (!methodDecl)
436 touchedMap[fieldDecl] = nullptr;
437 return;
439 if (methodDecl->getParent() != fieldDecl->getParent())
441 touchedMap[fieldDecl] = nullptr;
442 return;
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)
453 assert(arg);
454 if (arg->isValueDependent())
455 return false;
456 return arg->isCXX11ConstantExpr(compiler.getASTContext());
459 loplugin::Plugin::Registration<FieldCanBeLocal> X("fieldcanbelocal", false);
462 #endif
464 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */