[en_US] Added 13 autocorrect words
[LibreOffice.git] / compilerplugins / clang / unusedmember.cxx
blobbcd9a8593a68fd306413ed932d615490fc8cff99
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 "plugin.hxx"
26 namespace
28 // Whether the CXXRecordDecl itself or one of its enclosing classes is a template:
29 bool isTemplated(CXXRecordDecl const* decl)
31 if (decl->getDescribedClassTemplate() != nullptr)
33 return true;
35 if (auto const d = dyn_cast<CXXRecordDecl>(decl->getParent()))
37 return isTemplated(d);
39 return false;
42 bool isWarnUnusedType(QualType type)
44 if (auto const t = type->getAs<RecordType>())
46 if (t->getDecl()->hasAttr<WarnUnusedAttr>())
48 return true;
51 return loplugin::isExtraWarnUnusedType(type);
54 class UnusedMember final : public loplugin::FilteringPlugin<UnusedMember>
56 public:
57 explicit UnusedMember(loplugin::InstantiationData const& data)
58 : FilteringPlugin(data)
62 #if CLANG_VERSION < 60000
64 bool TraverseAlignedAttr(AlignedAttr* attr)
66 bool ret = FilteringPlugin::TraverseAlignedAttr(attr);
67 PostTraverseAlignedAttr(attr, ret);
68 return ret;
71 bool PostTraverseAlignedAttr(AlignedAttr* attr, bool run)
73 if (!run)
75 return false;
77 if (attr->isAlignmentExpr())
79 if (!TraverseStmt(attr->getAlignmentExpr()))
81 return false;
84 else if (auto const tsi = attr->getAlignmentType())
86 if (!TraverseTypeLoc(tsi->getTypeLoc()))
88 return false;
91 return true;
94 #endif
96 bool VisitCXXRecordDecl(CXXRecordDecl const* decl) //TODO: non-CXX RecordDecl?
98 if (ignoreLocation(decl))
100 return true;
102 if (!decl->isThisDeclarationADefinition())
104 return true;
106 if (!handler.isAllRelevantCodeDefined(decl))
108 return true;
110 if (!compiler.getSourceManager().isInMainFile(decl->getLocation()))
112 // include/rtl/instance.hxx declares entities in an unnamed namespace
113 return true;
115 if (isTemplated(decl) || isa<ClassTemplatePartialSpecializationDecl>(decl))
117 return true;
119 if (decl->isUnion() && decl->getIdentifier() == nullptr)
121 return true; //TODO
123 for (auto i = decl->decls_begin(); i != decl->decls_end(); ++i)
125 auto const d = *i;
126 if (d->isImplicit() || isa<AccessSpecDecl>(d) || isa<UsingDecl>(d))
128 //TODO: only filter out UsingDecls that are actually used (if only to silence
129 // -Woverloaded-virtual)
130 continue;
132 if (isa<ClassTemplateDecl>(d) || isa<FunctionTemplateDecl>(d))
134 //TODO: only filter out ones that are not instantiated at all
135 continue;
137 if (auto const d1 = dyn_cast<FriendDecl>(d))
139 //TODO: determine whether the friendship is actually required
140 auto const d2 = d1->getFriendDecl();
141 if (d2 == nullptr)
142 { // happens for "friend class C;"
143 continue;
145 if (auto const d3 = dyn_cast<FunctionDecl>(d2))
147 #if 0 //TODO: friend function definitions are not marked as referenced even if used?
148 if (!d3->isThisDeclarationADefinition()) //TODO: do this check for all kinds?
149 #endif
151 continue;
155 if (d->isReferenced())
157 continue;
159 if (d->hasAttr<UnusedAttr>())
161 continue;
163 // Check individual members instead of the whole CXXRecordDecl for coming from a macro,
164 // as CppUnit's CPPUNIT_TEST_SUITE_END (cppunit/extensions/HelperMacros.h) contains a
165 // partial member list ending in
167 // private: /* dummy typedef so that the macro can still end with ';'*/
168 // typedef int CppUnitDummyTypedefForSemiColonEnding__
170 if (compiler.getSourceManager().isMacroBodyExpansion(d->getLocation()))
172 return true;
174 if (auto const d1 = dyn_cast<FieldDecl>(d))
176 if (d1->isUnnamedBitfield())
178 continue;
180 if (!isWarnWhenUnusedType(d1->getType()))
182 continue;
184 deferred_.insert(d1);
185 continue;
187 if (auto const d1 = dyn_cast<FunctionDecl>(d))
189 if (d1->isDeletedAsWritten()) // TODO: just isDeleted?
191 continue;
193 if (d1->isExplicitlyDefaulted())
195 continue;
198 else if (auto const d2 = dyn_cast<TagDecl>(d))
200 if (d2->getIdentifier() == nullptr)
202 continue;
204 if (isa<EnumDecl>(d2))
206 deferred_.insert(d2);
207 continue;
210 else if (auto const d3 = dyn_cast<TypedefNameDecl>(d))
212 // Some types, like (specializations of) std::iterator_traits, have specific
213 // requirements on their members; only covers std::iterator_traits for now (TODO:
214 // check that at least some member is actually used)
215 // (isa<ClassTemplatePartialSpecializationDecl>(decl) is already filtered out
216 // above):
217 if (isa<ClassTemplateSpecializationDecl>(decl)
218 && loplugin::DeclCheck(decl).Struct("iterator_traits").StdNamespace())
220 auto const id = d3->getIdentifier();
221 assert(id != nullptr);
222 auto const n = id->getName();
223 if (n == "difference_type" || n == "iterator_category" || n == "pointer"
224 || n == "reference" || n == "value_type")
226 continue;
230 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
231 << d->getSourceRange();
233 return true;
236 bool VisitOffsetOfExpr(OffsetOfExpr const* expr)
238 if (ignoreLocation(expr))
240 return true;
242 recordRecordDeclAndBases(expr->getTypeSourceInfo()->getType()->castAs<RecordType>());
243 return true;
246 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const* expr)
248 if (ignoreLocation(expr))
250 return true;
252 switch (expr->getKind())
254 case UETT_SizeOf:
255 case UETT_AlignOf:
256 #if CLANG_VERSION >= 80000
257 case UETT_PreferredAlignOf:
258 #endif
259 break;
260 default:
261 return true;
263 if (!expr->isArgumentType())
265 return true;
267 auto t = expr->getArgumentType();
268 if (auto const t1 = t->getAs<ReferenceType>())
270 t = t1->getPointeeType();
272 if (auto const t1 = t->getAsArrayTypeUnsafe())
274 t = compiler.getASTContext().getBaseElementType(t1);
276 if (auto const t1 = t->getAs<RecordType>())
278 recordRecordDeclAndBases(t1);
280 return true;
283 // Handling implicit, C-style, static and reinterpret casts between void* and record types
284 // (though reinterpret_cast would be ruled out by loplugin:redundantcast):
285 bool VisitCastExpr(CastExpr const* expr)
287 if (ignoreLocation(expr))
289 return true;
291 auto const t1 = expr->getType();
292 auto const t2 = expr->getSubExprAsWritten()->getType();
293 if (loplugin::TypeCheck(t1).Pointer().Void())
295 recordCastedRecordDecl(t2);
297 else if (loplugin::TypeCheck(t2).Pointer().Void())
299 recordCastedRecordDecl(t1);
301 return true;
304 bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr const* expr)
306 if (ignoreLocation(expr))
308 return true;
310 recordCastedRecordDecl(expr->getTypeAsWritten());
311 recordCastedRecordDecl(expr->getSubExprAsWritten()->getType());
312 return true;
315 bool VisitElaboratedTypeLoc(ElaboratedTypeLoc tloc)
317 if (ignoreLocation(tloc))
319 return true;
321 auto const tl = tloc.getNamedTypeLoc().getAs<TagTypeLoc>();
322 if (tl.isNull())
324 return true;
326 if (tl.isDefinition())
328 return true;
330 if (auto const d = dyn_cast<EnumDecl>(tl.getDecl()))
332 // For some reason, using an elaborated type specifier in (at least) a FieldDecl, as in
334 // enum E { ... };
335 // enum E e;
337 // doesn't cause the EnumDecl to be marked as referenced. (This should fix it, but note
338 // the warning at <https://github.com/llvm/llvm-project/commit/
339 // b96ec568715450106b4f1dd4a20c1c14e9bca6c4#diff-019094457f96a6ed0ee072731d447073R396>:
340 // "[...] a type written 'struct foo' should be represented as an ElaboratedTypeLoc. We
341 // currently only do that when C++ is enabled [...]"
342 deferred_.erase(d->getCanonicalDecl());
344 return true;
347 void postRun() override
349 for (auto const d : deferred_)
351 if (auto const d1 = dyn_cast<FieldDecl>(d))
353 bool layout = false;
354 for (auto d2 = d1->getParent();;)
356 if (layout_.find(d2->getCanonicalDecl()) != layout_.end())
358 layout = true;
359 break;
361 // Heuristic to recursively check parent RecordDecl if given RecordDecl is
362 // unnamed and either an anonymous struct (or union, but which are already
363 // filtered out anyway), or defined in a non-static data member declaration
364 // (TODO: which is erroneously approximated here with getTypedefNameForAnonDecl
365 // for now, which fails to filter out RecordDecls in static data member
366 // declarations):
367 if (!(d2->getDeclName().isEmpty()
368 && (d2->isAnonymousStructOrUnion()
369 || d2->getTypedefNameForAnonDecl() == nullptr)))
371 break;
373 d2 = dyn_cast<RecordDecl>(d2->getParent());
374 if (d2 == nullptr)
376 break;
379 if (layout)
381 continue;
384 report(DiagnosticsEngine::Warning, "unused class member", d->getLocation())
385 << d->getSourceRange();
389 private:
390 void run() override
392 if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
394 postRun();
398 bool isWarnWhenUnusedType(QualType type)
400 auto t = type;
401 if (auto const t1 = t->getAs<ReferenceType>())
403 t = t1->getPointeeType();
405 return t.isTrivialType(compiler.getASTContext()) || isWarnUnusedType(t);
408 void recordRecordDeclAndBases(RecordType const* type)
410 auto const d1 = type->getDecl();
411 if (!layout_.insert(d1->getCanonicalDecl()).second)
413 return;
415 if (auto const d2 = dyn_cast_or_null<CXXRecordDecl>(d1->getDefinition()))
417 for (auto i = d2->bases_begin(); i != d2->bases_end(); ++i)
419 recordRecordDeclAndBases(i->getType()->castAs<RecordType>());
421 //TODO: doesn't iterate vbases, but presence of such would run counter to the layout
422 // heuristic anyway
426 void recordCastedRecordDecl(QualType type)
428 for (auto t = type;;)
430 if (auto const t1 = t->getAs<clang::PointerType>())
432 t = t1->getPointeeType();
433 continue;
435 if (auto const t1 = t->getAs<RecordType>())
437 recordRecordDeclAndBases(t1);
439 break;
443 // RecordDecls whose layout (i.e., contained FieldDecls) must presumably not be changed:
444 std::set<TagDecl const*> layout_;
446 std::set<Decl const*> deferred_;
449 loplugin::Plugin::Registration<UnusedMember> unusedmember("unusedmember");
452 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */