1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
15 #include "config_clang.h"
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:
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
44 std::string parentClass
;
45 std::string fieldName
;
46 std::string sourceLocation
;
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
>
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
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";
86 myfile
.open( WORKDIR
"/loplugin.unusedenumconstants.log", std::ios::app
| std::ios::out
);
92 for (const MyFieldInfo
& s
: writeSet
)
93 report(DiagnosticsEngine::Warning
, "write %0", s
.loc
)
95 for (const MyFieldInfo
& s
: readSet
)
96 report(DiagnosticsEngine::Warning
, "read %0", s
.loc
)
101 bool shouldVisitTemplateInstantiations () const { return true; }
102 bool shouldVisitImplicitCode() const { return true; }
104 bool VisitEnumConstantDecl( const EnumConstantDecl
* );
105 bool VisitDeclRefExpr( const DeclRefExpr
* );
107 MyFieldInfo
niceName(const EnumConstantDecl
*);
110 MyFieldInfo
UnusedEnumConstants::niceName(const EnumConstantDecl
* enumConstantDecl
)
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
);
131 bool UnusedEnumConstants::VisitEnumConstantDecl( const EnumConstantDecl
* enumConstantDecl
)
133 enumConstantDecl
= enumConstantDecl
->getCanonicalDecl();
134 if (ignoreLocation( enumConstantDecl
)) {
137 // ignore stuff that forms part of the stable URE interface
138 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(enumConstantDecl
->getLocation()))) {
142 definitionSet
.insert(niceName(enumConstantDecl
));
146 bool UnusedEnumConstants::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
148 auto enumConstantDecl
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl());
149 if (!enumConstantDecl
) {
152 enumConstantDecl
= enumConstantDecl
->getCanonicalDecl();
153 if (ignoreLocation(enumConstantDecl
)) {
156 // ignore stuff that forms part of the stable URE interface
157 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(enumConstantDecl
->getLocation()))) {
161 const Stmt
* parent
= declRefExpr
;
162 const Stmt
* child
= nullptr;
166 parent
= getParentStmt(parent
);
172 // Could probably do better here.
173 // Sometimes this is a constructor-initialiser-expression, so just make a pessimistic assumption.
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
184 if (auto innerOperatorCall
= dyn_cast
<CXXOperatorCallExpr
>(operatorCall
->getArg(1)->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit()))
186 found
= innerOperatorCall
->getOperator() == OO_Tilde
;
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
)
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
)
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
))
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
))
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
))
233 else if (const BinaryOperator
* binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
235 if (BinaryOperator::isAssignmentOp(binaryOp
->getOpcode())) {
237 } else if (BinaryOperator::isComparisonOp(binaryOp
->getOpcode())) {
243 else if (isa
<ConditionalOperator
>(parent
))
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
))
256 else if (isa
<CXXDefaultArgExpr
>(parent
))
258 // TODO this could be improved
261 else if (isa
<DeclRefExpr
>(parent
))
263 // slightly weird case I saw in basegfx where the enum is being used as a template param
266 else if (isa
<MemberExpr
>(parent
))
270 else if (isa
<ParenListExpr
>(parent
))
274 else if (isa
<CXXDefaultInitExpr
>(parent
))
278 else if (isa
<UnresolvedLookupExpr
>(parent
)
279 || isa
<CompoundStmt
>(parent
))
289 // to let me know if I missed something
293 report( DiagnosticsEngine::Warning
,
294 "unhandled clang AST node type",
295 parent
->getBeginLoc());
299 writeSet
.insert(niceName(enumConstantDecl
));
302 readSet
.insert(niceName(enumConstantDecl
));
307 loplugin::Plugin::Registration
< UnusedEnumConstants
> X("unusedenumconstants", false);
311 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */