nss: upgrade to release 3.73
[LibreOffice.git] / compilerplugins / clang / unusedmember.cxx
blob1d3017134892525049c71911dd6d5b16d86a2628
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 "config_clang.h"
23 #include "check.hxx"
24 #include "compat.hxx"
25 #include "plugin.hxx"
27 namespace
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)
34 return true;
36 if (auto const d = dyn_cast<CXXRecordDecl>(decl->getParent()))
38 return isTemplated(d);
40 return false;
43 bool isWarnUnusedType(QualType type)
45 if (auto const t = type->getAs<RecordType>())
47 if (t->getDecl()->hasAttr<WarnUnusedAttr>())
49 return true;
52 return loplugin::isExtraWarnUnusedType(type);
55 class UnusedMember final : public loplugin::FilteringPlugin<UnusedMember>
57 public:
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);
69 return ret;
72 bool PostTraverseAlignedAttr(AlignedAttr* attr, bool run)
74 if (!run)
76 return false;
78 if (attr->isAlignmentExpr())
80 if (!TraverseStmt(attr->getAlignmentExpr()))
82 return false;
85 else if (auto const tsi = attr->getAlignmentType())
87 if (!TraverseTypeLoc(tsi->getTypeLoc()))
89 return false;
92 return true;
95 #endif
97 bool VisitCXXRecordDecl(CXXRecordDecl const* decl) //TODO: non-CXX RecordDecl?
99 if (ignoreLocation(decl))
101 return true;
103 if (!decl->isThisDeclarationADefinition())
105 return true;
107 if (!handler.isAllRelevantCodeDefined(decl))
109 return true;
111 if (!compiler.getSourceManager().isInMainFile(decl->getLocation()))
113 // include/rtl/instance.hxx declares entities in an unnamed namespace
114 return true;
116 if (isTemplated(decl) || isa<ClassTemplatePartialSpecializationDecl>(decl))
118 return true;
120 if (decl->isUnion() && decl->getIdentifier() == nullptr)
122 return true; //TODO
124 for (auto i = decl->decls_begin(); i != decl->decls_end(); ++i)
126 auto const d = *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)
131 continue;
133 if (isa<ClassTemplateDecl>(d) || isa<FunctionTemplateDecl>(d))
135 //TODO: only filter out ones that are not instantiated at all
136 continue;
138 if (auto const d1 = dyn_cast<FriendDecl>(d))
140 //TODO: determine whether the friendship is actually required
141 auto const d2 = d1->getFriendDecl();
142 if (d2 == nullptr)
143 { // happens for "friend class C;"
144 continue;
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?
150 #endif
152 continue;
156 if (d->isReferenced())
158 continue;
160 if (d->hasAttr<UnusedAttr>())
162 continue;
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()))
173 return true;
175 if (auto const d1 = dyn_cast<FieldDecl>(d))
177 if (d1->isUnnamedBitfield())
179 continue;
181 if (!isWarnWhenUnusedType(d1->getType()))
183 continue;
185 deferred_.insert(d1);
186 continue;
188 if (auto const d1 = dyn_cast<FunctionDecl>(d))
190 if (d1->isDeletedAsWritten()) // TODO: just isDeleted?
192 continue;
194 if (d1->isExplicitlyDefaulted())
196 continue;
199 else if (auto const d2 = dyn_cast<TagDecl>(d))
201 if (d2->getIdentifier() == nullptr)
203 continue;
205 if (isa<EnumDecl>(d2))
207 deferred_.insert(d2);
208 continue;
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
217 // above):
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")
227 continue;
231 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
232 << d->getSourceRange();
234 return true;
237 bool VisitOffsetOfExpr(OffsetOfExpr const* expr)
239 if (ignoreLocation(expr))
241 return true;
243 auto const t1 = expr->getTypeSourceInfo()->getType();
244 RecordDecl const* d;
245 if (auto const t2 = t1->getAs<InjectedClassNameType>())
247 d = t2->getDecl();
249 else
251 d = t1->castAs<RecordType>()->getDecl();
253 recordRecordDeclAndBases(d);
254 return true;
257 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const* expr)
259 if (ignoreLocation(expr))
261 return true;
263 switch (expr->getKind())
265 case UETT_SizeOf:
266 case UETT_AlignOf:
267 #if CLANG_VERSION >= 80000
268 case UETT_PreferredAlignOf:
269 #endif
270 break;
271 default:
272 return true;
274 if (!expr->isArgumentType())
276 return true;
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());
291 return true;
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))
300 return true;
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);
312 return true;
315 bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr const* expr)
317 if (ignoreLocation(expr))
319 return true;
321 recordCastedRecordDecl(expr->getTypeAsWritten());
322 recordCastedRecordDecl(expr->getSubExprAsWritten()->getType());
323 return true;
326 bool VisitElaboratedTypeLoc(ElaboratedTypeLoc tloc)
328 if (ignoreLocation(tloc))
330 return true;
332 auto const tl = tloc.getNamedTypeLoc().getAs<TagTypeLoc>();
333 if (tl.isNull())
335 return true;
337 if (tl.isDefinition())
339 return true;
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
345 // enum E { ... };
346 // enum E e;
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());
355 return true;
358 void postRun() override
360 for (auto const d : deferred_)
362 if (auto const d1 = dyn_cast<FieldDecl>(d))
364 bool layout = false;
365 for (auto d2 = d1->getParent();;)
367 if (layout_.find(d2->getCanonicalDecl()) != layout_.end())
369 layout = true;
370 break;
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
377 // declarations):
378 if (!(d2->getDeclName().isEmpty()
379 && (d2->isAnonymousStructOrUnion()
380 || d2->getTypedefNameForAnonDecl() == nullptr)))
382 break;
384 d2 = dyn_cast<RecordDecl>(d2->getParent());
385 if (d2 == nullptr)
387 break;
390 if (layout)
392 continue;
395 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
396 << d->getSourceRange();
400 private:
401 void run() override
403 if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
405 postRun();
409 bool isWarnWhenUnusedType(QualType type)
411 auto t = 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)
423 return;
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
432 // heuristic anyway
436 void recordCastedRecordDecl(QualType type)
438 for (auto t = type;;)
440 if (auto const t1 = t->getAs<clang::PointerType>())
442 t = t1->getPointeeType();
443 continue;
445 if (auto const t1 = t->getAs<RecordType>())
447 recordRecordDeclAndBases(t1->getDecl());
449 break;
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: */