Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / unusedmember.cxx
blob9cf40d7212599061b2c618f712ecbf7c683e4057
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 // 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.
18 #include <cassert>
19 #include <set>
21 #include "check.hxx"
22 #include "compat.hxx"
23 #include "plugin.hxx"
25 namespace
27 // Whether the CXXRecordDecl itself or one of its enclosing classes is a template:
28 bool isTemplated(CXXRecordDecl const* decl)
30 if (decl->getDescribedClassTemplate() != nullptr)
32 return true;
34 if (auto const d = dyn_cast<CXXRecordDecl>(decl->getParent()))
36 return isTemplated(d);
38 return false;
41 bool isWarnUnusedType(QualType type)
43 if (auto const t = type->getAs<RecordType>())
45 if (t->getDecl()->hasAttr<WarnUnusedAttr>())
47 return true;
50 return loplugin::isExtraWarnUnusedType(type);
53 class UnusedMember final : public loplugin::FilteringPlugin<UnusedMember>
55 public:
56 explicit UnusedMember(loplugin::InstantiationData const& data)
57 : FilteringPlugin(data)
61 bool VisitDeclaratorDecl(DeclaratorDecl const* decl)
63 // For declarations like
65 // enum E { ... } e;
67 // it may be that the declaration of E is not marked as referenced even though the
68 // declaration of e clearly references it:
69 if (auto const t = decl->getType()->getAs<EnumType>())
71 deferred_.erase(t->getDecl());
73 return true;
76 bool VisitCXXRecordDecl(CXXRecordDecl const* decl) //TODO: non-CXX RecordDecl?
78 if (ignoreLocation(decl))
80 return true;
82 if (!decl->isThisDeclarationADefinition())
84 return true;
86 if (!handler.isAllRelevantCodeDefined(decl))
88 return true;
90 if (!compiler.getSourceManager().isInMainFile(decl->getLocation()))
92 // include/rtl/instance.hxx declares entities in an unnamed namespace
93 return true;
95 if (isTemplated(decl) || isa<ClassTemplatePartialSpecializationDecl>(decl))
97 return true;
99 if (decl->isUnion() && decl->getIdentifier() == nullptr)
101 return true; //TODO
103 for (auto i = decl->decls_begin(); i != decl->decls_end(); ++i)
105 auto const d = *i;
106 if (d->isImplicit() || isa<AccessSpecDecl>(d) || isa<UsingDecl>(d))
108 //TODO: only filter out UsingDecls that are actually used (if only to silence
109 // -Woverloaded-virtual)
110 continue;
112 if (isa<ClassTemplateDecl>(d) || isa<FunctionTemplateDecl>(d))
114 //TODO: only filter out ones that are not instantiated at all
115 continue;
117 if (auto const d1 = dyn_cast<FriendDecl>(d))
119 //TODO: determine whether the friendship is actually required
120 auto const d2 = d1->getFriendDecl();
121 if (d2 == nullptr)
122 { // happens for "friend class C;"
123 continue;
125 if (auto const d3 = dyn_cast<FunctionDecl>(d2))
127 #if 0 //TODO: friend function definitions are not marked as referenced even if used?
128 if (!d3->isThisDeclarationADefinition()) //TODO: do this check for all kinds?
129 #endif
131 continue;
135 if (d->isReferenced())
137 continue;
139 if (d->hasAttr<UnusedAttr>())
141 continue;
143 // Check individual members instead of the whole CXXRecordDecl for coming from a macro,
144 // as CppUnit's CPPUNIT_TEST_SUITE_END (cppunit/extensions/HelperMacros.h) contains a
145 // partial member list ending in
147 // private: /* dummy typedef so that the macro can still end with ';'*/
148 // typedef int CppUnitDummyTypedefForSemiColonEnding__
150 if (compiler.getSourceManager().isMacroBodyExpansion(d->getLocation()))
152 return true;
154 if (auto const d1 = dyn_cast<FieldDecl>(d))
156 if (d1->isUnnamedBitfield())
158 continue;
160 if (!isWarnWhenUnusedType(d1->getType()))
162 continue;
164 deferred_.insert(d1);
165 continue;
167 if (auto const d1 = dyn_cast<FunctionDecl>(d))
169 if (d1->isDeletedAsWritten()) // TODO: just isDeleted?
171 continue;
173 if (d1->isExplicitlyDefaulted())
175 continue;
178 else if (auto const d2 = dyn_cast<TagDecl>(d))
180 if (d2->getIdentifier() == nullptr)
182 continue;
184 if (isa<EnumDecl>(d2))
186 deferred_.insert(d2);
187 continue;
190 else if (auto const d3 = dyn_cast<TypedefNameDecl>(d))
192 // Some types, like (specializations of) std::iterator_traits, have specific
193 // requirements on their members; only covers std::iterator_traits for now (TODO:
194 // check that at least some member is actually used)
195 // (isa<ClassTemplatePartialSpecializationDecl>(decl) is already filtered out
196 // above):
197 if (isa<ClassTemplateSpecializationDecl>(decl)
198 && loplugin::DeclCheck(decl).Struct("iterator_traits").StdNamespace())
200 auto const id = d3->getIdentifier();
201 assert(id != nullptr);
202 auto const n = id->getName();
203 if (n == "difference_type" || n == "iterator_category" || n == "pointer"
204 || n == "reference" || n == "value_type")
206 continue;
210 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
211 << d->getSourceRange();
213 return true;
216 bool VisitOffsetOfExpr(OffsetOfExpr const* expr)
218 if (ignoreLocation(expr))
220 return true;
222 auto const t1 = expr->getTypeSourceInfo()->getType();
223 if (t1->isTemplateTypeParmType())
225 return true;
227 RecordDecl const* d;
228 if (auto const t2 = t1->getAs<InjectedClassNameType>())
230 d = t2->getDecl();
232 else
234 d = t1->castAs<RecordType>()->getDecl();
236 recordRecordDeclAndBases(d);
237 return true;
240 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const* expr)
242 if (ignoreLocation(expr))
244 return true;
246 switch (expr->getKind())
248 case UETT_SizeOf:
249 case UETT_AlignOf:
250 case UETT_PreferredAlignOf:
251 break;
252 default:
253 return true;
255 if (!expr->isArgumentType())
257 return true;
259 auto t = expr->getArgumentType();
260 if (auto const t1 = t->getAs<ReferenceType>())
262 t = t1->getPointeeType();
264 if (auto const t1 = t->getAsArrayTypeUnsafe())
266 t = compiler.getASTContext().getBaseElementType(t1);
268 if (auto const t1 = t->getAs<RecordType>())
270 recordRecordDeclAndBases(t1->getDecl());
272 return true;
275 // Handling implicit, C-style, static and reinterpret casts between void* and record types
276 // (though reinterpret_cast would be ruled out by loplugin:redundantcast):
277 bool VisitCastExpr(CastExpr const* expr)
279 if (ignoreLocation(expr))
281 return true;
283 auto const t1 = expr->getType();
284 auto const t2 = compat::getSubExprAsWritten(expr)->getType();
285 if (loplugin::TypeCheck(t1).Pointer().Void())
287 recordCastedRecordDecl(t2);
289 else if (loplugin::TypeCheck(t2).Pointer().Void())
291 recordCastedRecordDecl(t1);
293 return true;
296 bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr const* expr)
298 if (ignoreLocation(expr))
300 return true;
302 recordCastedRecordDecl(expr->getTypeAsWritten());
303 recordCastedRecordDecl(expr->getSubExprAsWritten()->getType());
304 return true;
307 bool VisitElaboratedTypeLoc(ElaboratedTypeLoc tloc)
309 if (ignoreLocation(tloc))
311 return true;
313 auto const tl = tloc.getNamedTypeLoc().getAs<TagTypeLoc>();
314 if (tl.isNull())
316 return true;
318 if (tl.isDefinition())
320 return true;
322 if (auto const d = dyn_cast<EnumDecl>(tl.getDecl()))
324 // For some reason, using an elaborated type specifier in (at least) a FieldDecl, as in
326 // enum E { ... };
327 // enum E e;
329 // doesn't cause the EnumDecl to be marked as referenced. (This should fix it, but note
330 // the warning at <https://github.com/llvm/llvm-project/commit/
331 // b96ec568715450106b4f1dd4a20c1c14e9bca6c4#diff-019094457f96a6ed0ee072731d447073R396>:
332 // "[...] a type written 'struct foo' should be represented as an ElaboratedTypeLoc. We
333 // currently only do that when C++ is enabled [...]"
334 deferred_.erase(d->getCanonicalDecl());
336 return true;
339 void postRun() override
341 for (auto const d : deferred_)
343 if (auto const d1 = dyn_cast<FieldDecl>(d))
345 bool layout = false;
346 for (auto d2 = d1->getParent();;)
348 if (layout_.find(d2->getCanonicalDecl()) != layout_.end())
350 layout = true;
351 break;
353 // Heuristic to recursively check parent RecordDecl if given RecordDecl is
354 // unnamed and either an anonymous struct (or union, but which are already
355 // filtered out anyway), or defined in a non-static data member declaration
356 // (TODO: which is erroneously approximated here with getTypedefNameForAnonDecl
357 // for now, which fails to filter out RecordDecls in static data member
358 // declarations):
359 if (!(d2->getDeclName().isEmpty()
360 && (d2->isAnonymousStructOrUnion()
361 || d2->getTypedefNameForAnonDecl() == nullptr)))
363 break;
365 d2 = dyn_cast<RecordDecl>(d2->getParent());
366 if (d2 == nullptr)
368 break;
371 if (layout)
373 continue;
376 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
377 << d->getSourceRange();
381 private:
382 void run() override
384 if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
386 postRun();
390 bool isWarnWhenUnusedType(QualType type)
392 auto t = type;
393 if (auto const t1 = t->getAs<ReferenceType>())
395 t = t1->getPointeeType();
397 return t.isTrivialType(compiler.getASTContext()) || isWarnUnusedType(t);
400 void recordRecordDeclAndBases(RecordDecl const* decl)
402 if (!layout_.insert(decl->getCanonicalDecl()).second)
404 return;
406 if (auto const d2 = dyn_cast_or_null<CXXRecordDecl>(decl->getDefinition()))
408 for (auto i = d2->bases_begin(); i != d2->bases_end(); ++i)
410 recordRecordDeclAndBases(i->getType()->castAs<RecordType>()->getDecl());
412 //TODO: doesn't iterate vbases, but presence of such would run counter to the layout
413 // heuristic anyway
417 void recordCastedRecordDecl(QualType type)
419 for (auto t = type;;)
421 if (auto const t1 = t->getAs<clang::PointerType>())
423 t = t1->getPointeeType();
424 continue;
426 if (auto const t1 = t->getAs<RecordType>())
428 recordRecordDeclAndBases(t1->getDecl());
430 break;
434 // RecordDecls whose layout (i.e., contained FieldDecls) must presumably not be changed:
435 std::set<TagDecl const*> layout_;
437 std::set<Decl const*> deferred_;
440 loplugin::Plugin::Registration<UnusedMember> unusedmember("unusedmember");
443 // Cannot be shared, uses TraverseStmt().
445 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */