1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
10 // A more aggressive check for unused C struct/C++ class members than what plain Clang offers. On
11 // the one hand, unlike -Wunused-private-field, it warns about all members regardless of access
12 // specifiers, if all code that can use a class has been seen. On the other hand, it warns about
13 // all kinds of members. But it uses some heuristics (the type showing up in sizeof, alignof,
14 // offsetof, certain casts) to determine that seemingly unused data members are probably used after
15 // all; the used heuristics were enough to not require any explicit [[maybe_unused]] decorations
16 // across the existing code base.
21 #include "config_clang.h"
29 // Whether the CXXRecordDecl itself or one of its enclosing classes is a template:
30 bool isTemplated(CXXRecordDecl
const* decl
)
32 if (decl
->getDescribedClassTemplate() != nullptr)
36 if (auto const d
= dyn_cast
<CXXRecordDecl
>(decl
->getParent()))
38 return isTemplated(d
);
43 bool isWarnUnusedType(QualType type
)
45 if (auto const t
= type
->getAs
<RecordType
>())
47 if (t
->getDecl()->hasAttr
<WarnUnusedAttr
>())
52 return loplugin::isExtraWarnUnusedType(type
);
55 class UnusedMember final
: public loplugin::FilteringPlugin
<UnusedMember
>
58 explicit UnusedMember(loplugin::InstantiationData
const& data
)
59 : FilteringPlugin(data
)
63 #if CLANG_VERSION < 60000
65 bool TraverseAlignedAttr(AlignedAttr
* attr
)
67 bool ret
= FilteringPlugin::TraverseAlignedAttr(attr
);
68 PostTraverseAlignedAttr(attr
, ret
);
72 bool PostTraverseAlignedAttr(AlignedAttr
* attr
, bool run
)
78 if (attr
->isAlignmentExpr())
80 if (!TraverseStmt(attr
->getAlignmentExpr()))
85 else if (auto const tsi
= attr
->getAlignmentType())
87 if (!TraverseTypeLoc(tsi
->getTypeLoc()))
97 bool VisitCXXRecordDecl(CXXRecordDecl
const* decl
) //TODO: non-CXX RecordDecl?
99 if (ignoreLocation(decl
))
103 if (!decl
->isThisDeclarationADefinition())
107 if (!handler
.isAllRelevantCodeDefined(decl
))
111 if (!compiler
.getSourceManager().isInMainFile(decl
->getLocation()))
113 // include/rtl/instance.hxx declares entities in an unnamed namespace
116 if (isTemplated(decl
) || isa
<ClassTemplatePartialSpecializationDecl
>(decl
))
120 if (decl
->isUnion() && decl
->getIdentifier() == nullptr)
124 for (auto i
= decl
->decls_begin(); i
!= decl
->decls_end(); ++i
)
127 if (d
->isImplicit() || isa
<AccessSpecDecl
>(d
) || isa
<UsingDecl
>(d
))
129 //TODO: only filter out UsingDecls that are actually used (if only to silence
130 // -Woverloaded-virtual)
133 if (isa
<ClassTemplateDecl
>(d
) || isa
<FunctionTemplateDecl
>(d
))
135 //TODO: only filter out ones that are not instantiated at all
138 if (auto const d1
= dyn_cast
<FriendDecl
>(d
))
140 //TODO: determine whether the friendship is actually required
141 auto const d2
= d1
->getFriendDecl();
143 { // happens for "friend class C;"
146 if (auto const d3
= dyn_cast
<FunctionDecl
>(d2
))
148 #if 0 //TODO: friend function definitions are not marked as referenced even if used?
149 if (!d3
->isThisDeclarationADefinition()) //TODO: do this check for all kinds?
156 if (d
->isReferenced())
160 if (d
->hasAttr
<UnusedAttr
>())
164 // Check individual members instead of the whole CXXRecordDecl for coming from a macro,
165 // as CppUnit's CPPUNIT_TEST_SUITE_END (cppunit/extensions/HelperMacros.h) contains a
166 // partial member list ending in
168 // private: /* dummy typedef so that the macro can still end with ';'*/
169 // typedef int CppUnitDummyTypedefForSemiColonEnding__
171 if (compiler
.getSourceManager().isMacroBodyExpansion(d
->getLocation()))
175 if (auto const d1
= dyn_cast
<FieldDecl
>(d
))
177 if (d1
->isUnnamedBitfield())
181 if (!isWarnWhenUnusedType(d1
->getType()))
185 deferred_
.insert(d1
);
188 if (auto const d1
= dyn_cast
<FunctionDecl
>(d
))
190 if (d1
->isDeletedAsWritten()) // TODO: just isDeleted?
194 if (d1
->isExplicitlyDefaulted())
199 else if (auto const d2
= dyn_cast
<TagDecl
>(d
))
201 if (d2
->getIdentifier() == nullptr)
205 if (isa
<EnumDecl
>(d2
))
207 deferred_
.insert(d2
);
211 else if (auto const d3
= dyn_cast
<TypedefNameDecl
>(d
))
213 // Some types, like (specializations of) std::iterator_traits, have specific
214 // requirements on their members; only covers std::iterator_traits for now (TODO:
215 // check that at least some member is actually used)
216 // (isa<ClassTemplatePartialSpecializationDecl>(decl) is already filtered out
218 if (isa
<ClassTemplateSpecializationDecl
>(decl
)
219 && loplugin::DeclCheck(decl
).Struct("iterator_traits").StdNamespace())
221 auto const id
= d3
->getIdentifier();
222 assert(id
!= nullptr);
223 auto const n
= id
->getName();
224 if (n
== "difference_type" || n
== "iterator_category" || n
== "pointer"
225 || n
== "reference" || n
== "value_type")
231 report(DiagnosticsEngine::Warning
, "unused class member", d
->getLocation())
232 << d
->getSourceRange();
237 bool VisitOffsetOfExpr(OffsetOfExpr
const* expr
)
239 if (ignoreLocation(expr
))
243 auto const t1
= expr
->getTypeSourceInfo()->getType();
245 if (auto const t2
= t1
->getAs
<InjectedClassNameType
>())
251 d
= t1
->castAs
<RecordType
>()->getDecl();
253 recordRecordDeclAndBases(d
);
257 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const* expr
)
259 if (ignoreLocation(expr
))
263 switch (expr
->getKind())
267 #if CLANG_VERSION >= 80000
268 case UETT_PreferredAlignOf
:
274 if (!expr
->isArgumentType())
278 auto t
= expr
->getArgumentType();
279 if (auto const t1
= t
->getAs
<ReferenceType
>())
281 t
= t1
->getPointeeType();
283 if (auto const t1
= t
->getAsArrayTypeUnsafe())
285 t
= compiler
.getASTContext().getBaseElementType(t1
);
287 if (auto const t1
= t
->getAs
<RecordType
>())
289 recordRecordDeclAndBases(t1
->getDecl());
294 // Handling implicit, C-style, static and reinterpret casts between void* and record types
295 // (though reinterpret_cast would be ruled out by loplugin:redundantcast):
296 bool VisitCastExpr(CastExpr
const* expr
)
298 if (ignoreLocation(expr
))
302 auto const t1
= expr
->getType();
303 auto const t2
= compat::getSubExprAsWritten(expr
)->getType();
304 if (loplugin::TypeCheck(t1
).Pointer().Void())
306 recordCastedRecordDecl(t2
);
308 else if (loplugin::TypeCheck(t2
).Pointer().Void())
310 recordCastedRecordDecl(t1
);
315 bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr
const* expr
)
317 if (ignoreLocation(expr
))
321 recordCastedRecordDecl(expr
->getTypeAsWritten());
322 recordCastedRecordDecl(expr
->getSubExprAsWritten()->getType());
326 bool VisitElaboratedTypeLoc(ElaboratedTypeLoc tloc
)
328 if (ignoreLocation(tloc
))
332 auto const tl
= tloc
.getNamedTypeLoc().getAs
<TagTypeLoc
>();
337 if (tl
.isDefinition())
341 if (auto const d
= dyn_cast
<EnumDecl
>(tl
.getDecl()))
343 // For some reason, using an elaborated type specifier in (at least) a FieldDecl, as in
348 // doesn't cause the EnumDecl to be marked as referenced. (This should fix it, but note
349 // the warning at <https://github.com/llvm/llvm-project/commit/
350 // b96ec568715450106b4f1dd4a20c1c14e9bca6c4#diff-019094457f96a6ed0ee072731d447073R396>:
351 // "[...] a type written 'struct foo' should be represented as an ElaboratedTypeLoc. We
352 // currently only do that when C++ is enabled [...]"
353 deferred_
.erase(d
->getCanonicalDecl());
358 void postRun() override
360 for (auto const d
: deferred_
)
362 if (auto const d1
= dyn_cast
<FieldDecl
>(d
))
365 for (auto d2
= d1
->getParent();;)
367 if (layout_
.find(d2
->getCanonicalDecl()) != layout_
.end())
372 // Heuristic to recursively check parent RecordDecl if given RecordDecl is
373 // unnamed and either an anonymous struct (or union, but which are already
374 // filtered out anyway), or defined in a non-static data member declaration
375 // (TODO: which is erroneously approximated here with getTypedefNameForAnonDecl
376 // for now, which fails to filter out RecordDecls in static data member
378 if (!(d2
->getDeclName().isEmpty()
379 && (d2
->isAnonymousStructOrUnion()
380 || d2
->getTypedefNameForAnonDecl() == nullptr)))
384 d2
= dyn_cast
<RecordDecl
>(d2
->getParent());
395 report(DiagnosticsEngine::Warning
, "unused class member", d
->getLocation())
396 << d
->getSourceRange();
403 if (TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
409 bool isWarnWhenUnusedType(QualType type
)
412 if (auto const t1
= t
->getAs
<ReferenceType
>())
414 t
= t1
->getPointeeType();
416 return t
.isTrivialType(compiler
.getASTContext()) || isWarnUnusedType(t
);
419 void recordRecordDeclAndBases(RecordDecl
const* decl
)
421 if (!layout_
.insert(decl
->getCanonicalDecl()).second
)
425 if (auto const d2
= dyn_cast_or_null
<CXXRecordDecl
>(decl
->getDefinition()))
427 for (auto i
= d2
->bases_begin(); i
!= d2
->bases_end(); ++i
)
429 recordRecordDeclAndBases(i
->getType()->castAs
<RecordType
>()->getDecl());
431 //TODO: doesn't iterate vbases, but presence of such would run counter to the layout
436 void recordCastedRecordDecl(QualType type
)
438 for (auto t
= type
;;)
440 if (auto const t1
= t
->getAs
<clang::PointerType
>())
442 t
= t1
->getPointeeType();
445 if (auto const t1
= t
->getAs
<RecordType
>())
447 recordRecordDeclAndBases(t1
->getDecl());
453 // RecordDecls whose layout (i.e., contained FieldDecls) must presumably not be changed:
454 std::set
<TagDecl
const*> layout_
;
456 std::set
<Decl
const*> deferred_
;
459 loplugin::Plugin::Registration
<UnusedMember
> unusedmember("unusedmember");
462 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */