cURL: follow redirects
[LibreOffice.git] / compilerplugins / clang / unusedfields.cxx
blob94a1cb6300d9dc88792c86df0e36f1d4dd975dc7
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 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <fstream>
14 #include <set>
15 #include "plugin.hxx"
16 #include "compat.hxx"
18 /**
19 This performs two analyses:
20 (1) look for unused fields
21 (2) look for fields that are write-only
23 We dmp a list of calls to methods, and a list of field definitions.
24 Then we will post-process the 2 lists and find the set of unused methods.
26 Be warned that it produces around 5G of log file.
28 The process goes something like this:
29 $ make check
30 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedfields' check
31 $ ./compilerplugins/clang/unusedfields.py
33 and then
34 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done
35 to auto-remove the method declarations
37 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
38 to get it to work :-)
42 namespace {
44 struct MyFieldInfo
46 std::string parentClass;
47 std::string fieldName;
48 std::string fieldType;
49 std::string sourceLocation;
50 std::string access;
52 bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
54 return std::tie(lhs.parentClass, lhs.fieldName)
55 < std::tie(rhs.parentClass, rhs.fieldName);
59 // try to limit the voluminous output a little
60 static std::set<MyFieldInfo> touchedSet;
61 static std::set<MyFieldInfo> touchedFromOutsideSet;
62 static std::set<MyFieldInfo> readFromSet;
63 static std::set<MyFieldInfo> definitionSet;
66 class UnusedFields:
67 public RecursiveASTVisitor<UnusedFields>, public loplugin::Plugin
69 public:
70 explicit UnusedFields(InstantiationData const & data): Plugin(data) {}
72 virtual void run() override
74 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
76 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
77 // writing to the same logfile
78 std::string output;
79 for (const MyFieldInfo & s : touchedSet)
80 output += "touch:\t" + s.parentClass + "\t" + s.fieldName + "\n";
81 for (const MyFieldInfo & s : touchedFromOutsideSet)
82 output += "outside:\t" + s.parentClass + "\t" + s.fieldName + "\n";
83 for (const MyFieldInfo & s : readFromSet)
84 output += "read:\t" + s.parentClass + "\t" + s.fieldName + "\n";
85 for (const MyFieldInfo & s : definitionSet)
87 output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.fieldType + "\t" + s.sourceLocation + "\n";
89 ofstream myfile;
90 myfile.open( SRCDIR "/loplugin.unusedfields.log", ios::app | ios::out);
91 myfile << output;
92 myfile.close();
95 bool shouldVisitTemplateInstantiations () const { return true; }
96 bool shouldVisitImplicitCode() const { return true; }
98 bool VisitFieldDecl( const FieldDecl* );
99 bool VisitMemberExpr( const MemberExpr* );
100 bool VisitDeclRefExpr( const DeclRefExpr* );
101 private:
102 MyFieldInfo niceName(const FieldDecl*);
103 void checkForTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr, const MyFieldInfo& fieldInfo);
106 MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
108 MyFieldInfo aInfo;
110 const RecordDecl* recordDecl = fieldDecl->getParent();
112 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
114 if (cxxRecordDecl->getTemplateInstantiationPattern())
115 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
116 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
118 else
119 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
121 aInfo.fieldName = fieldDecl->getNameAsString();
122 aInfo.fieldType = fieldDecl->getType().getAsString();
124 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldDecl->getLocation() );
125 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
126 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
127 normalizeDotDotInFilePath(aInfo.sourceLocation);
129 switch (fieldDecl->getAccess())
131 case AS_public: aInfo.access = "public"; break;
132 case AS_private: aInfo.access = "private"; break;
133 case AS_protected: aInfo.access = "protected"; break;
134 default: aInfo.access = "unknown"; break;
137 return aInfo;
140 bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
142 fieldDecl = fieldDecl->getCanonicalDecl();
143 if (ignoreLocation( fieldDecl )) {
144 return true;
146 // ignore stuff that forms part of the stable URE interface
147 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
148 return true;
151 QualType type = fieldDecl->getType();
152 // unwrap array types
153 while (type->isArrayType())
154 type = type->getAsArrayTypeUnsafe()->getElementType();
156 if( CXXRecordDecl* recordDecl = type->getAsCXXRecordDecl() )
158 bool warn_unused = recordDecl->hasAttr<WarnUnusedAttr>();
159 if( !warn_unused )
161 string n = recordDecl->getQualifiedNameAsString();
162 // Check some common non-LO types.
163 if( n == "std::string" || n == "std::basic_string"
164 || n == "std::list" || n == "std::__debug::list"
165 || n == "std::vector" || n == "std::__debug::vector" )
166 warn_unused = true;
168 if (!warn_unused)
169 return true;
172 definitionSet.insert(niceName(fieldDecl));
173 return true;
176 bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
178 const ValueDecl* decl = memberExpr->getMemberDecl();
179 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
180 if (!fieldDecl) {
181 return true;
183 fieldDecl = fieldDecl->getCanonicalDecl();
184 if (ignoreLocation(fieldDecl)) {
185 return true;
187 // ignore stuff that forms part of the stable URE interface
188 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
189 return true;
192 MyFieldInfo fieldInfo = niceName(fieldDecl);
194 // for the touched-from-outside analysis
196 checkForTouchedFromOutside(fieldDecl, memberExpr, fieldInfo);
198 // for the write-only analysis
200 const Stmt* child = memberExpr;
201 const Stmt* parent = parentStmt(memberExpr);
202 // walk up the tree until we find something interesting
203 bool bPotentiallyReadFrom = false;
204 bool bDump = false;
205 do {
206 if (!parent) {
207 return true;
209 if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
210 || isa<ExprWithCleanups>(parent) || isa<UnaryOperator>(parent))
212 child = parent;
213 parent = parentStmt(parent);
215 else if (isa<CaseStmt>(parent))
217 bPotentiallyReadFrom = dyn_cast<CaseStmt>(parent)->getLHS() == child
218 || dyn_cast<CaseStmt>(parent)->getRHS() == child;
219 break;
221 else if (isa<IfStmt>(parent))
223 bPotentiallyReadFrom = dyn_cast<IfStmt>(parent)->getCond() == child;
224 break;
226 else if (isa<DoStmt>(parent))
228 bPotentiallyReadFrom = dyn_cast<DoStmt>(parent)->getCond() == child;
229 break;
231 else if (isa<ReturnStmt>(parent) || isa<CXXConstructExpr>(parent) || isa<CallExpr>(parent)
232 || isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent) || isa<ArraySubscriptExpr>(parent)
233 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
234 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
235 || isa<BinaryOperator>(parent) || isa<CXXDependentScopeMemberExpr>(parent)
236 || isa<UnresolvedMemberExpr>(parent)
237 || isa<MaterializeTemporaryExpr>(parent)) //???
239 bPotentiallyReadFrom = true;
240 break;
242 else if (isa<CXXDeleteExpr>(parent)
243 || isa<UnaryExprOrTypeTraitExpr>(parent)
244 || isa<CXXUnresolvedConstructExpr>(parent) || isa<CompoundStmt>(parent)
245 || isa<CXXTypeidExpr>(parent) || isa<DefaultStmt>(parent))
247 break;
249 else {
250 bPotentiallyReadFrom = true;
251 bDump = true;
252 break;
254 } while (true);
255 if (bDump)
257 report(
258 DiagnosticsEngine::Warning,
259 "oh dear, what can the matter be?",
260 memberExpr->getLocStart())
261 << memberExpr->getSourceRange();
262 parent->dump();
264 if (bPotentiallyReadFrom)
265 readFromSet.insert(fieldInfo);
266 return true;
269 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
271 const Decl* decl = declRefExpr->getDecl();
272 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
273 if (!fieldDecl) {
274 return true;
276 fieldDecl = fieldDecl->getCanonicalDecl();
277 if (ignoreLocation(fieldDecl)) {
278 return true;
280 // ignore stuff that forms part of the stable URE interface
281 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
282 return true;
284 MyFieldInfo fieldInfo = niceName(fieldDecl);
285 touchedSet.insert(fieldInfo);
286 checkForTouchedFromOutside(fieldDecl, declRefExpr, fieldInfo);
287 return true;
290 void UnusedFields::checkForTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr, const MyFieldInfo& fieldInfo) {
291 const FunctionDecl* memberExprParentFunction = parentFunctionDecl(memberExpr);
292 const CXXMethodDecl* methodDecl = dyn_cast_or_null<CXXMethodDecl>(memberExprParentFunction);
294 // it's touched from somewhere outside a class
295 if (!methodDecl) {
296 touchedSet.insert(fieldInfo);
297 touchedFromOutsideSet.insert(fieldInfo);
298 return;
301 auto constructorDecl = dyn_cast<CXXConstructorDecl>(methodDecl);
302 if (methodDecl->isCopyAssignmentOperator() || methodDecl->isMoveAssignmentOperator()) {
303 // ignore move/copy operator, it's self->self
304 } else if (constructorDecl && (constructorDecl->isCopyConstructor() || constructorDecl->isMoveConstructor())) {
305 // ignore move/copy constructor, it's self->self
306 } else {
307 touchedSet.insert(fieldInfo);
308 if (memberExprParentFunction->getParent() != fieldDecl->getParent()) {
309 touchedFromOutsideSet.insert(fieldInfo);
314 loplugin::Plugin::Registration< UnusedFields > X("unusedfields", false);
318 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */