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/.
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=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
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 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
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";
84 myfile
.open( WORKDIR
"/loplugin.unusedenumconstants.log", std::ios::app
| std::ios::out
);
90 for (const MyFieldInfo
& s
: writeSet
)
91 report(DiagnosticsEngine::Warning
, "write %0", s
.loc
)
93 for (const MyFieldInfo
& s
: readSet
)
94 report(DiagnosticsEngine::Warning
, "read %0", s
.loc
)
99 bool shouldVisitTemplateInstantiations () const { return true; }
100 bool shouldVisitImplicitCode() const { return true; }
102 bool VisitEnumConstantDecl( const EnumConstantDecl
* );
103 bool VisitDeclRefExpr( const DeclRefExpr
* );
105 MyFieldInfo
niceName(const EnumConstantDecl
*);
108 MyFieldInfo
UnusedEnumConstants::niceName(const EnumConstantDecl
* enumConstantDecl
)
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
);
129 bool UnusedEnumConstants::VisitEnumConstantDecl( const EnumConstantDecl
* enumConstantDecl
)
131 enumConstantDecl
= enumConstantDecl
->getCanonicalDecl();
132 if (ignoreLocation( enumConstantDecl
)) {
135 // ignore stuff that forms part of the stable URE interface
136 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(enumConstantDecl
->getLocation()))) {
140 definitionSet
.insert(niceName(enumConstantDecl
));
144 bool UnusedEnumConstants::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
146 auto enumConstantDecl
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl());
147 if (!enumConstantDecl
) {
150 enumConstantDecl
= enumConstantDecl
->getCanonicalDecl();
151 if (ignoreLocation(enumConstantDecl
)) {
154 // ignore stuff that forms part of the stable URE interface
155 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(enumConstantDecl
->getLocation()))) {
159 const Stmt
* parent
= declRefExpr
;
160 const Stmt
* child
= nullptr;
164 parent
= getParentStmt(parent
);
170 // Could probably do better here.
171 // Sometimes this is a constructor-initialiser-expression, so just make a pessimistic assumption.
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
182 if (auto innerOperatorCall
= dyn_cast
<CXXOperatorCallExpr
>(operatorCall
->getArg(1)->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit()))
184 found
= innerOperatorCall
->getOperator() == OO_Tilde
;
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
)
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
)
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
))
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
))
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
))
231 else if (const BinaryOperator
* binaryOp
= dyn_cast
<BinaryOperator
>(parent
))
233 if (BinaryOperator::isAssignmentOp(binaryOp
->getOpcode())) {
235 } else if (BinaryOperator::isComparisonOp(binaryOp
->getOpcode())) {
241 else if (isa
<ConditionalOperator
>(parent
))
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
)
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
<UnresolvedLookupExpr
>(parent
)
271 || isa
<CompoundStmt
>(parent
))
281 // to let me know if I missed something
285 report( DiagnosticsEngine::Warning
,
286 "unhandled clang AST node type",
287 compat::getBeginLoc(parent
));
291 writeSet
.insert(niceName(enumConstantDecl
));
294 readSet
.insert(niceName(enumConstantDecl
));
299 loplugin::Plugin::Registration
< UnusedEnumConstants
> X("unusedenumconstants", false);
303 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */