lok: getSlideShowInfo: interactions: check that properties are available
[LibreOffice.git] / compilerplugins / clang / unusedenumconstants.cxx
blob692a37e4e755f39a71da265d15ac864bdb0b73d4
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 "config_clang.h"
16 #include "plugin.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 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 handler.enableTreeWideAnalysisMode();
72 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
74 if (!isUnitTestMode())
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 : definitionSet)
80 output += "definition:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.sourceLocation + "\n";
81 for (const MyFieldInfo & s : writeSet)
82 output += "write:\t" + s.parentClass + "\t" + s.fieldName + "\n";
83 for (const MyFieldInfo & s : readSet)
84 output += "read:\t" + s.parentClass + "\t" + s.fieldName + "\n";
85 std::ofstream myfile;
86 myfile.open( WORKDIR "/loplugin.unusedenumconstants.log", std::ios::app | std::ios::out);
87 myfile << output;
88 myfile.close();
90 else
92 for (const MyFieldInfo& s : writeSet)
93 report(DiagnosticsEngine::Warning, "write %0", s.loc)
94 << s.fieldName;
95 for (const MyFieldInfo& s : readSet)
96 report(DiagnosticsEngine::Warning, "read %0", s.loc)
97 << s.fieldName;
101 bool shouldVisitTemplateInstantiations () const { return true; }
102 bool shouldVisitImplicitCode() const { return true; }
104 bool VisitEnumConstantDecl( const EnumConstantDecl * );
105 bool VisitDeclRefExpr( const DeclRefExpr * );
106 private:
107 MyFieldInfo niceName(const EnumConstantDecl*);
110 MyFieldInfo UnusedEnumConstants::niceName(const EnumConstantDecl* enumConstantDecl)
112 MyFieldInfo aInfo;
114 aInfo.parentClass = enumConstantDecl->getType().getAsString();
115 aInfo.fieldName = enumConstantDecl->getNameAsString();
116 // sometimes the name (if it's anonymous thing) contains the full path of the build folder, which we don't need
117 size_t idx = aInfo.fieldName.find(SRCDIR);
118 if (idx != std::string::npos) {
119 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
122 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( enumConstantDecl->getLocation() );
123 StringRef name = getFilenameOfLocation(expansionLoc);
124 aInfo.loc = expansionLoc;
125 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
126 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
128 return aInfo;
131 bool UnusedEnumConstants::VisitEnumConstantDecl( const EnumConstantDecl* enumConstantDecl )
133 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
134 if (ignoreLocation( enumConstantDecl )) {
135 return true;
137 // ignore stuff that forms part of the stable URE interface
138 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
139 return true;
142 definitionSet.insert(niceName(enumConstantDecl));
143 return true;
146 bool UnusedEnumConstants::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
148 auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl());
149 if (!enumConstantDecl) {
150 return true;
152 enumConstantDecl = enumConstantDecl->getCanonicalDecl();
153 if (ignoreLocation(enumConstantDecl)) {
154 return true;
156 // ignore stuff that forms part of the stable URE interface
157 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(enumConstantDecl->getLocation()))) {
158 return true;
161 const Stmt * parent = declRefExpr;
162 const Stmt * child = nullptr;
164 walk_up:
165 child = parent;
166 parent = getParentStmt(parent);
167 bool bWrite = false;
168 bool bRead = false;
169 bool bDump = false;
170 if (!parent)
172 // Could probably do better here.
173 // Sometimes this is a constructor-initialiser-expression, so just make a pessimistic assumption.
174 bWrite = true;
176 else if (const CXXOperatorCallExpr * operatorCall = dyn_cast<CXXOperatorCallExpr>(parent))
178 auto oo = operatorCall->getOperator();
179 if (oo == OO_AmpEqual)
181 // Ignore a common pattern that does not introduce any new information, merely removes
182 // information: foo &= ~Enum6::Top
183 bool found = false;
184 if (auto innerOperatorCall = dyn_cast<CXXOperatorCallExpr>(operatorCall->getArg(1)->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit()))
186 found = innerOperatorCall->getOperator() == OO_Tilde;
188 if (!found)
189 bWrite = true;
191 // if assignment op
192 else if (oo == OO_Equal || oo == OO_StarEqual || oo == OO_SlashEqual || oo == OO_PercentEqual
193 || oo == OO_PlusEqual || oo == OO_MinusEqual || oo == OO_LessLessEqual
194 || oo == OO_CaretEqual || oo == OO_PipeEqual)
195 bWrite = true;
196 // else if comparison op
197 else if (oo == OO_AmpAmp || oo == OO_PipePipe || oo == OO_Subscript
198 || oo == OO_Less || oo == OO_Greater || oo == OO_LessEqual || oo == OO_GreaterEqual || oo == OO_EqualEqual || oo == OO_ExclaimEqual)
199 bRead = true;
200 else
201 goto walk_up;
203 else if (const CXXMemberCallExpr * memberCall = dyn_cast<CXXMemberCallExpr>(parent))
205 // happens a lot with o3tl::typed_flags
206 if (*memberCall->child_begin() == child)
208 if (auto conversionDecl = dyn_cast<CXXConversionDecl>(memberCall->getMethodDecl()))
210 if (conversionDecl->getConversionType()->isSpecificBuiltinType(clang::BuiltinType::Bool))
211 bRead = true;
212 else
213 goto walk_up;
215 else
216 goto walk_up;
218 else
219 bWrite = true;
221 else if (isa<CallExpr>(parent) || isa<InitListExpr>(parent) || isa<ArraySubscriptExpr>(parent)
222 || isa<ReturnStmt>(parent) || isa<DeclStmt>(parent)
223 || isa<CXXConstructExpr>(parent)
224 || isa<CXXThrowExpr>(parent))
226 bWrite = true;
228 else if (isa<CaseStmt>(parent) || isa<SwitchStmt>(parent) || isa<IfStmt>(parent)
229 || isa<WhileStmt>(parent) || isa<DoStmt>(parent) || isa<ForStmt>(parent) || isa<DefaultStmt>(parent))
231 bRead = true;
233 else if (const BinaryOperator * binaryOp = dyn_cast<BinaryOperator>(parent))
235 if (BinaryOperator::isAssignmentOp(binaryOp->getOpcode())) {
236 bWrite = true;
237 } else if (BinaryOperator::isComparisonOp(binaryOp->getOpcode())) {
238 bRead = true;
239 } else {
240 goto walk_up;
243 else if (isa<ConditionalOperator>(parent))
245 goto walk_up;
247 else if (isa<CastExpr>(parent) || isa<UnaryOperator>(parent)
248 || isa<ParenExpr>(parent)
249 || isa<MaterializeTemporaryExpr>(parent)
250 || isa<ExprWithCleanups>(parent)
251 || isa<ConstantExpr>(parent)
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<ParenListExpr>(parent))
272 goto walk_up;
274 else if (isa<CXXDefaultInitExpr>(parent))
276 goto walk_up;
278 else if (isa<UnresolvedLookupExpr>(parent)
279 || isa<CompoundStmt>(parent))
281 bRead = true;
282 bWrite = true;
284 else
286 bDump = true;
289 // to let me know if I missed something
290 if (bDump) {
291 parent->dump();
292 declRefExpr->dump();
293 report( DiagnosticsEngine::Warning,
294 "unhandled clang AST node type",
295 parent->getBeginLoc());
298 if (bWrite) {
299 writeSet.insert(niceName(enumConstantDecl));
301 if (bRead) {
302 readSet.insert(niceName(enumConstantDecl));
304 return true;
307 loplugin::Plugin::Registration< UnusedEnumConstants > X("unusedenumconstants", false);
311 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */