Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / unusedenumconstants.cxx
blobfd159cea5ab5ec0a1cada42770f6628258e6a9e9
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 looks for unused enum constants
21 We search for 3 things
22 (a) constants that are declared but never used
23 (b) constants only used in a "read" fashion i.e. we compare stuff against them, but we never store a value anywhere
24 (c) constants only used in a "write" fashion i.e. we store a value, but never check for that value
26 (a) is fairly reliable but (b) and (c) will need some checking before acting on.
28 Be warned that it produces around 5G of log file.
30 The process goes something like this:
31 $ make check
32 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedenumconstants' check
33 $ ./compilerplugins/clang/unusedenumconstants.py
35 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
36 to get it to work :-)
40 namespace {
42 struct MyFieldInfo
44 std::string parentClass;
45 std::string fieldName;
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);
55 // try to limit the voluminous output a little
56 static std::set<MyFieldInfo> definitionSet;
57 static std::set<MyFieldInfo> writeSet;
58 static std::set<MyFieldInfo> readSet;
61 class UnusedEnumConstants:
62 public RecursiveASTVisitor<UnusedEnumConstants>, public loplugin::Plugin
64 public:
65 explicit UnusedEnumConstants(loplugin::InstantiationData const & data): Plugin(data) {}
67 virtual void run() override
69 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
71 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
72 // writing to the same logfile
73 std::string output;
74 for (const MyFieldInfo & s : definitionSet)
75 output += "definition:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.sourceLocation + "\n";
76 for (const MyFieldInfo & s : writeSet)
77 output += "write:\t" + s.parentClass + "\t" + s.fieldName + "\n";
78 for (const MyFieldInfo & s : readSet)
79 output += "read:\t" + s.parentClass + "\t" + s.fieldName + "\n";
80 std::ofstream myfile;
81 myfile.open( WORKDIR "/loplugin.unusedenumconstants.log", std::ios::app | std::ios::out);
82 myfile << output;
83 myfile.close();
86 bool shouldVisitTemplateInstantiations () const { return true; }
87 bool shouldVisitImplicitCode() const { return true; }
89 bool VisitEnumConstantDecl( const EnumConstantDecl * );
90 bool VisitDeclRefExpr( const DeclRefExpr * );
91 private:
92 MyFieldInfo niceName(const EnumConstantDecl*);
95 MyFieldInfo UnusedEnumConstants::niceName(const EnumConstantDecl* enumConstantDecl)
97 MyFieldInfo aInfo;
99 aInfo.parentClass = enumConstantDecl->getType().getAsString();
100 aInfo.fieldName = enumConstantDecl->getNameAsString();
101 // sometimes the name (if it's anonymous thing) contains the full path of the build folder, which we don't need
102 size_t idx = aInfo.fieldName.find(SRCDIR);
103 if (idx != std::string::npos) {
104 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
107 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( enumConstantDecl->getLocation() );
108 StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
109 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
110 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
112 return aInfo;
115 bool UnusedEnumConstants::VisitEnumConstantDecl( const EnumConstantDecl* enumConstantDecl )
117 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
118 if (ignoreLocation( enumConstantDecl )) {
119 return true;
121 // ignore stuff that forms part of the stable URE interface
122 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
123 return true;
126 definitionSet.insert(niceName(enumConstantDecl));
127 return true;
130 bool UnusedEnumConstants::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
132 auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl());
133 if (!enumConstantDecl) {
134 return true;
136 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
137 if (ignoreLocation(enumConstantDecl)) {
138 return true;
140 // ignore stuff that forms part of the stable URE interface
141 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
142 return true;
145 const Stmt * parent = declRefExpr;
146 try_again:
147 parent = getParentStmt(parent);
148 bool bWrite = false;
149 bool bRead = false;
150 bool bDump = false;
151 if (!parent)
153 // Could probably do better here.
154 // Sometimes this is a constructor-initialiser-expression, so just make a pessimistic assumption.
155 bWrite = true;
156 } else if (isa<CallExpr>(parent) || isa<InitListExpr>(parent) || isa<ArraySubscriptExpr>(parent)
157 || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
158 || isa<CXXConstructExpr>(parent)
159 || isa<CXXThrowExpr>(parent))
161 bWrite = true;
162 } else if (isa<CaseStmt>(parent) || isa<SwitchStmt>(parent))
164 bRead = true;
165 } else if (const BinaryOperator * binaryOp = dyn_cast<BinaryOperator>(parent))
167 if (BinaryOperator::isAssignmentOp(binaryOp->getOpcode())) {
168 bWrite = true;
169 } else {
170 bRead = true;
172 } else if (const CXXOperatorCallExpr * operatorCall = dyn_cast<CXXOperatorCallExpr>(parent))
174 auto oo = operatorCall->getOperator();
175 if (oo == OO_Equal
176 || (oo >= OO_PlusEqual && oo <= OO_GreaterGreaterEqual)) {
177 bWrite = true;
178 } else {
179 bRead = true;
181 } else if (isa<CastExpr>(parent) || isa<UnaryOperator>(parent)
182 || isa<ConditionalOperator>(parent) || isa<ParenExpr>(parent)
183 || isa<MaterializeTemporaryExpr>(parent)
184 || isa<ExprWithCleanups>(parent))
186 goto try_again;
187 } else if (isa<CXXDefaultArgExpr>(parent))
189 // TODO this could be improved
190 bWrite = true;
191 } else if (isa<DeclRefExpr>(parent))
193 // slightly weird case I saw in basegfx where the enum is being used as a template param
194 bWrite = true;
195 } else if (isa<MemberExpr>(parent))
197 // slightly weird case I saw in sc where the enum is being used as a template param
198 bWrite = true;
199 } else if (isa<UnresolvedLookupExpr>(parent))
201 bRead = true;
202 bWrite = true;
203 } else {
204 bDump = true;
207 // to let me know if I missed something
208 if (bDump) {
209 parent->dump();
210 declRefExpr->dump();
211 report( DiagnosticsEngine::Warning,
212 "unhandled clang AST node type",
213 parent->getLocStart());
216 if (bWrite) {
217 writeSet.insert(niceName(enumConstantDecl));
219 if (bRead) {
220 readSet.insert(niceName(enumConstantDecl));
222 return true;
225 loplugin::Plugin::Registration< UnusedEnumConstants > X("unusedenumconstants", false);
229 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */