bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / unusedenumconstants.cxx
blob4ae1f52f27132085812eda7923a71f3b36878906
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;
47 SourceLocation loc;
49 bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
51 return std::tie(lhs.parentClass, lhs.fieldName)
52 < std::tie(rhs.parentClass, rhs.fieldName);
56 // try to limit the voluminous output a little
57 static std::set<MyFieldInfo> definitionSet;
58 static std::set<MyFieldInfo> writeSet;
59 static std::set<MyFieldInfo> readSet;
62 class UnusedEnumConstants:
63 public loplugin::FilteringPlugin<UnusedEnumConstants>
65 public:
66 explicit UnusedEnumConstants(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
68 virtual void run() override
70 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
72 if (!isUnitTestMode())
74 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
75 // writing to the same logfile
76 std::string output;
77 for (const MyFieldInfo & s : definitionSet)
78 output += "definition:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.sourceLocation + "\n";
79 for (const MyFieldInfo & s : writeSet)
80 output += "write:\t" + s.parentClass + "\t" + s.fieldName + "\n";
81 for (const MyFieldInfo & s : readSet)
82 output += "read:\t" + s.parentClass + "\t" + s.fieldName + "\n";
83 std::ofstream myfile;
84 myfile.open( WORKDIR "/loplugin.unusedenumconstants.log", std::ios::app | std::ios::out);
85 myfile << output;
86 myfile.close();
88 else
90 for (const MyFieldInfo& s : writeSet)
91 report(DiagnosticsEngine::Warning, "write %0", s.loc)
92 << s.fieldName;
93 for (const MyFieldInfo& s : readSet)
94 report(DiagnosticsEngine::Warning, "read %0", s.loc)
95 << s.fieldName;
99 bool shouldVisitTemplateInstantiations () const { return true; }
100 bool shouldVisitImplicitCode() const { return true; }
102 bool VisitEnumConstantDecl( const EnumConstantDecl * );
103 bool VisitDeclRefExpr( const DeclRefExpr * );
104 private:
105 MyFieldInfo niceName(const EnumConstantDecl*);
108 MyFieldInfo UnusedEnumConstants::niceName(const EnumConstantDecl* enumConstantDecl)
110 MyFieldInfo aInfo;
112 aInfo.parentClass = enumConstantDecl->getType().getAsString();
113 aInfo.fieldName = enumConstantDecl->getNameAsString();
114 // sometimes the name (if it's anonymous thing) contains the full path of the build folder, which we don't need
115 size_t idx = aInfo.fieldName.find(SRCDIR);
116 if (idx != std::string::npos) {
117 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
120 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( enumConstantDecl->getLocation() );
121 StringRef name = getFilenameOfLocation(expansionLoc);
122 aInfo.loc = expansionLoc;
123 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
124 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
126 return aInfo;
129 bool UnusedEnumConstants::VisitEnumConstantDecl( const EnumConstantDecl* enumConstantDecl )
131 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
132 if (ignoreLocation( enumConstantDecl )) {
133 return true;
135 // ignore stuff that forms part of the stable URE interface
136 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
137 return true;
140 definitionSet.insert(niceName(enumConstantDecl));
141 return true;
144 bool UnusedEnumConstants::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
146 auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl());
147 if (!enumConstantDecl) {
148 return true;
150 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
151 if (ignoreLocation(enumConstantDecl)) {
152 return true;
154 // ignore stuff that forms part of the stable URE interface
155 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
156 return true;
159 const Stmt * parent = declRefExpr;
160 const Stmt * child = nullptr;
162 walk_up:
163 child = parent;
164 parent = getParentStmt(parent);
165 bool bWrite = false;
166 bool bRead = false;
167 bool bDump = false;
168 if (!parent)
170 // Could probably do better here.
171 // Sometimes this is a constructor-initialiser-expression, so just make a pessimistic assumption.
172 bWrite = true;
174 else if (const CXXOperatorCallExpr * operatorCall = dyn_cast<CXXOperatorCallExpr>(parent))
176 auto oo = operatorCall->getOperator();
177 if (oo == OO_AmpEqual)
179 // Ignore a common pattern that does not introduce any new information, merely removes
180 // information: foo &= ~Enum6::Top
181 bool found = false;
182 if (auto innerOperatorCall = dyn_cast<CXXOperatorCallExpr>(operatorCall->getArg(1)->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit()))
184 found = innerOperatorCall->getOperator() == OO_Tilde;
186 if (!found)
187 bWrite = true;
189 // if assignment op
190 else if (oo == OO_Equal || oo == OO_StarEqual || oo == OO_SlashEqual || oo == OO_PercentEqual
191 || oo == OO_PlusEqual || oo == OO_MinusEqual || oo == OO_LessLessEqual
192 || oo == OO_CaretEqual || oo == OO_PipeEqual)
193 bWrite = true;
194 // else if comparison op
195 else if (oo == OO_AmpAmp || oo == OO_PipePipe || oo == OO_Subscript
196 || oo == OO_Less || oo == OO_Greater || oo == OO_LessEqual || oo == OO_GreaterEqual || oo == OO_EqualEqual || oo == OO_ExclaimEqual)
197 bRead = true;
198 else
199 goto walk_up;
201 else if (const CXXMemberCallExpr * memberCall = dyn_cast<CXXMemberCallExpr>(parent))
203 // happens a lot with o3tl::typed_flags
204 if (*memberCall->child_begin() == child)
206 if (auto conversionDecl = dyn_cast<CXXConversionDecl>(memberCall->getMethodDecl()))
208 if (conversionDecl->getConversionType()->isSpecificBuiltinType(clang::BuiltinType::Bool))
209 bRead = true;
210 else
211 goto walk_up;
213 else
214 goto walk_up;
216 else
217 bWrite = true;
219 else if (isa<CallExpr>(parent) || isa<InitListExpr>(parent) || isa<ArraySubscriptExpr>(parent)
220 || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
221 || isa<CXXConstructExpr>(parent)
222 || isa<CXXThrowExpr>(parent))
224 bWrite = true;
226 else if (isa<CaseStmt>(parent) || isa<SwitchStmt>(parent) || isa<IfStmt>(parent)
227 || isa<WhileStmt>(parent) || isa<DoStmt>(parent) || isa<ForStmt>(parent) || isa<DefaultStmt>(parent))
229 bRead = true;
231 else if (const BinaryOperator * binaryOp = dyn_cast<BinaryOperator>(parent))
233 if (BinaryOperator::isAssignmentOp(binaryOp->getOpcode())) {
234 bWrite = true;
235 } else if (BinaryOperator::isComparisonOp(binaryOp->getOpcode())) {
236 bRead = true;
237 } else {
238 goto walk_up;
241 else if (isa<ConditionalOperator>(parent))
243 goto walk_up;
245 else if (isa<CastExpr>(parent) || isa<UnaryOperator>(parent)
246 || isa<ParenExpr>(parent)
247 || isa<MaterializeTemporaryExpr>(parent)
248 || isa<ExprWithCleanups>(parent)
249 #if CLANG_VERSION >= 80000
250 || isa<ConstantExpr>(parent)
251 #endif
252 || isa<CXXBindTemporaryExpr>(parent))
254 goto walk_up;
256 else if (isa<CXXDefaultArgExpr>(parent))
258 // TODO this could be improved
259 bWrite = true;
261 else if (isa<DeclRefExpr>(parent))
263 // slightly weird case I saw in basegfx where the enum is being used as a template param
264 bWrite = true;
266 else if (isa<MemberExpr>(parent))
268 goto walk_up;
270 else if (isa<UnresolvedLookupExpr>(parent)
271 || isa<CompoundStmt>(parent))
273 bRead = true;
274 bWrite = true;
276 else
278 bDump = true;
281 // to let me know if I missed something
282 if (bDump) {
283 parent->dump();
284 declRefExpr->dump();
285 report( DiagnosticsEngine::Warning,
286 "unhandled clang AST node type",
287 compat::getBeginLoc(parent));
290 if (bWrite) {
291 writeSet.insert(niceName(enumConstantDecl));
293 if (bRead) {
294 readSet.insert(niceName(enumConstantDecl));
296 return true;
299 loplugin::Plugin::Registration< UnusedEnumConstants > X("unusedenumconstants", false);
303 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */