compilerplugins/clang/fakebool.cxx | 114 ------- compilerplugins/clang/plugin.hxx | 9 compilerplugins/clang/pluginhandler.cxx | 106 ++++++ compilerplugins/clang/pluginhandler.hxx | 11 compilerplugins/clang/test/unusedmember.cxx | 158 ++++++++++ compilerplugins/clang/unusedmember.cxx | 416 +++++++++++++++++++++++++++ solenv/CompilerTest_compilerplugins_clang.mk | 1 7 files changed, 704 insertions(+), 111 deletions(-)
New commits: commit 6a10149c5fef13721e3f83727a828556f8e1ec9a Author: Stephan Bergmann <sberg...@redhat.com> AuthorDate: Wed Dec 4 14:32:15 2019 +0100 Commit: Stephan Bergmann <sberg...@redhat.com> CommitDate: Thu Dec 5 13:30:36 2019 +0100 New loplugin:unusedmember * See comment at head of compilerplugins/clang/unusedmember.cxx for description. * Moved isAllRelevantCodeDefined from loplugin:fakebool to PluginHandler for reuse. (Made it a member function so that it can reuse its two RecordCompleteMap instances across different loplugins. Probably safer lifecycle-wise to have them as PluginHandler members than to have them as static local variables in function isAllRelevantCodeDefined.) * Need Plugin::ignoreLocation overload for TypeLoc now, thanks to UnusedMember::VisitElaboratedTypeLoc. * UETT_PreferredAlignOf was split off UETT_AlignOf with <https://github.com/ llvm/llvm-project/commit/6822bd79ac43f267613f1615bf60407103e24dba> "PR26547: alignof should return ABI alignment, not preferred alignment". * RecursiveASTVisitor::TraverseAlignedAttr traverses into the attribute's argument only since <https://github.com/llvm/llvm-project/commit/ f26d551387f032e05e5e6551605b150f38c3f5b2> "Do not look through pack expansions when looking for unexpanded parameter packs". Change-Id: Ic2702b03d4567fa2533333766de7920f3c524a69 Reviewed-on: https://gerrit.libreoffice.org/84416 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sberg...@redhat.com> diff --git a/compilerplugins/clang/fakebool.cxx b/compilerplugins/clang/fakebool.cxx index 62fbc936e897..f50116b8ee88 100644 --- a/compilerplugins/clang/fakebool.cxx +++ b/compilerplugins/clang/fakebool.cxx @@ -23,114 +23,6 @@ namespace { -// BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp - -typedef llvm::DenseMap<const CXXRecordDecl*, bool> RecordCompleteMap; - -/// Returns true, if all methods and nested classes of the given -/// CXXRecordDecl are defined in this translation unit. -/// -/// Should only be called from ActOnEndOfTranslationUnit so that all -/// definitions are actually read. -static bool MethodsAndNestedClassesComplete(const CXXRecordDecl *RD, - RecordCompleteMap &MNCComplete) { - RecordCompleteMap::iterator Cache = MNCComplete.find(RD); - if (Cache != MNCComplete.end()) - return Cache->second; - if (!RD->isCompleteDefinition()) - return false; - bool Complete = true; - for (DeclContext::decl_iterator I = RD->decls_begin(), - E = RD->decls_end(); - I != E && Complete; ++I) { - if (const CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(*I)) - Complete = M->isDefined() || M->isDefaulted() || - (M->isPure() && !isa<CXXDestructorDecl>(M)); - else if (const FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(*I)) - // If the template function is marked as late template parsed at this - // point, it has not been instantiated and therefore we have not - // performed semantic analysis on it yet, so we cannot know if the type - // can be considered complete. - Complete = !F->getTemplatedDecl()->isLateTemplateParsed() && - F->getTemplatedDecl()->isDefined(); - else if (const CXXRecordDecl *R = dyn_cast<CXXRecordDecl>(*I)) { - if (R->isInjectedClassName()) - continue; - if (R->hasDefinition()) - Complete = MethodsAndNestedClassesComplete(R->getDefinition(), - MNCComplete); - else - Complete = false; - } - } - MNCComplete[RD] = Complete; - return Complete; -} - -/// Returns true, if the given CXXRecordDecl is fully defined in this -/// translation unit, i.e. all methods are defined or pure virtual and all -/// friends, friend functions and nested classes are fully defined in this -/// translation unit. -/// -/// Should only be called from ActOnEndOfTranslationUnit so that all -/// definitions are actually read. -static bool IsRecordFullyDefined(const CXXRecordDecl *RD, - RecordCompleteMap &RecordsComplete, - RecordCompleteMap &MNCComplete) { - RecordCompleteMap::iterator Cache = RecordsComplete.find(RD); - if (Cache != RecordsComplete.end()) - return Cache->second; - bool Complete = MethodsAndNestedClassesComplete(RD, MNCComplete); - for (CXXRecordDecl::friend_iterator I = RD->friend_begin(), - E = RD->friend_end(); - I != E && Complete; ++I) { - // Check if friend classes and methods are complete. - if (TypeSourceInfo *TSI = (*I)->getFriendType()) { - // Friend classes are available as the TypeSourceInfo of the FriendDecl. - if (CXXRecordDecl *FriendD = TSI->getType()->getAsCXXRecordDecl()) - Complete = MethodsAndNestedClassesComplete(FriendD, MNCComplete); - else - Complete = false; - } else { - // Friend functions are available through the NamedDecl of FriendDecl. - if (const FunctionDecl *FD = - dyn_cast<FunctionDecl>((*I)->getFriendDecl())) - Complete = FD->isDefined(); - else - // This is a template friend, give up. - Complete = false; - } - } - RecordsComplete[RD] = Complete; - return Complete; -} - -RecordCompleteMap RecordsComplete; -RecordCompleteMap MNCComplete; - -// END code copied from LLVM's clang/lib/Sema/Sema.cpp - -// Is all code that could see `decl` defined in this TU? -bool isAllRelevantCodeDefined(NamedDecl const * decl) { - switch (decl->getAccess()) { - case AS_protected: - if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) { - break; - } - LLVM_FALLTHROUGH; - case AS_private: - if (IsRecordFullyDefined( - cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete, MNCComplete)) - { - return true; - } - break; - default: - break; - } - return !decl->isExternallyVisible(); -} - enum FakeBoolKind { FBK_No, FBK_BOOL, FBK_First = FBK_BOOL, @@ -845,7 +737,7 @@ bool FakeBool::VisitParmVarDecl(ParmVarDecl const * decl) { FunctionDecl const * f = dyn_cast<FunctionDecl>(decl->getDeclContext()); if (f != nullptr) { // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool ); f = f->getCanonicalDecl(); - if (isAllRelevantCodeDefined(f) + if (handler.isAllRelevantCodeDefined(f) && !(hasCLanguageLinkageType(f) || (fbk == FBK_sal_Bool && isInUnoIncludeFile(f) && (!f->isInlined() || f->hasAttr<DeprecatedAttr>() @@ -916,7 +808,7 @@ bool FakeBool::VisitFieldDecl(FieldDecl const * decl) { if (k == FBK_No) { return true; } - if (!isAllRelevantCodeDefined(decl)) { + if (!handler.isAllRelevantCodeDefined(decl)) { return true; } if (k == FBK_sal_Bool @@ -951,7 +843,7 @@ bool FakeBool::VisitFunctionDecl(FunctionDecl const * decl) { auto const fbk = isFakeBool(decl->getReturnType().getNonReferenceType()); if (fbk != FBK_No && !(decl->isDeletedAsWritten() && isa<CXXConversionDecl>(decl)) - && isAllRelevantCodeDefined(decl)) + && handler.isAllRelevantCodeDefined(decl)) { FunctionDecl const * f = decl->getCanonicalDecl(); OverrideKind k = getOverrideKind(f); diff --git a/compilerplugins/clang/plugin.hxx b/compilerplugins/clang/plugin.hxx index ebadc645ef7a..d6df6b522e31 100644 --- a/compilerplugins/clang/plugin.hxx +++ b/compilerplugins/clang/plugin.hxx @@ -71,6 +71,7 @@ protected: { return handler.ignoreLocation(loc); } bool ignoreLocation( const Decl* decl ) const; bool ignoreLocation( const Stmt* stmt ) const; + bool ignoreLocation(TypeLoc tloc) const; CompilerInstance& compiler; PluginHandler& handler; /** @@ -223,6 +224,14 @@ bool Plugin::ignoreLocation( const Stmt* stmt ) const return compat::getBeginLoc(stmt).isValid() && ignoreLocation( compat::getBeginLoc(stmt)); } +inline bool Plugin::ignoreLocation(TypeLoc tloc) const +{ + // Invalid locations appear to happen at least with Clang 5.0.2 (but no longer with at least + // recent Clang 10 trunk): + auto const loc = tloc.getBeginLoc(); + return loc.isValid() && ignoreLocation(loc); +} + template< typename T > Plugin* Plugin::createHelper( const InstantiationData& data ) { diff --git a/compilerplugins/clang/pluginhandler.cxx b/compilerplugins/clang/pluginhandler.cxx index 43c9fb33c1c6..6d00cf22a8cf 100644 --- a/compilerplugins/clang/pluginhandler.cxx +++ b/compilerplugins/clang/pluginhandler.cxx @@ -384,6 +384,112 @@ void PluginHandler::HandleTranslationUnit( ASTContext& context ) #endif } +namespace { + +// BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp + +/// Returns true, if all methods and nested classes of the given +/// CXXRecordDecl are defined in this translation unit. +/// +/// Should only be called from ActOnEndOfTranslationUnit so that all +/// definitions are actually read. +static bool MethodsAndNestedClassesComplete(const CXXRecordDecl *RD, + RecordCompleteMap &MNCComplete) { + RecordCompleteMap::iterator Cache = MNCComplete.find(RD); + if (Cache != MNCComplete.end()) + return Cache->second; + if (!RD->isCompleteDefinition()) + return false; + bool Complete = true; + for (DeclContext::decl_iterator I = RD->decls_begin(), + E = RD->decls_end(); + I != E && Complete; ++I) { + if (const CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(*I)) + Complete = M->isDefined() || M->isDefaulted() || + (M->isPure() && !isa<CXXDestructorDecl>(M)); + else if (const FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(*I)) + // If the template function is marked as late template parsed at this + // point, it has not been instantiated and therefore we have not + // performed semantic analysis on it yet, so we cannot know if the type + // can be considered complete. + Complete = !F->getTemplatedDecl()->isLateTemplateParsed() && + F->getTemplatedDecl()->isDefined(); + else if (const CXXRecordDecl *R = dyn_cast<CXXRecordDecl>(*I)) { + if (R->isInjectedClassName()) + continue; + if (R->hasDefinition()) + Complete = MethodsAndNestedClassesComplete(R->getDefinition(), + MNCComplete); + else + Complete = false; + } + } + MNCComplete[RD] = Complete; + return Complete; +} + +/// Returns true, if the given CXXRecordDecl is fully defined in this +/// translation unit, i.e. all methods are defined or pure virtual and all +/// friends, friend functions and nested classes are fully defined in this +/// translation unit. +/// +/// Should only be called from ActOnEndOfTranslationUnit so that all +/// definitions are actually read. +static bool IsRecordFullyDefined(const CXXRecordDecl *RD, + RecordCompleteMap &RecordsComplete, + RecordCompleteMap &MNCComplete) { + RecordCompleteMap::iterator Cache = RecordsComplete.find(RD); + if (Cache != RecordsComplete.end()) + return Cache->second; + bool Complete = MethodsAndNestedClassesComplete(RD, MNCComplete); + for (CXXRecordDecl::friend_iterator I = RD->friend_begin(), + E = RD->friend_end(); + I != E && Complete; ++I) { + // Check if friend classes and methods are complete. + if (TypeSourceInfo *TSI = (*I)->getFriendType()) { + // Friend classes are available as the TypeSourceInfo of the FriendDecl. + if (CXXRecordDecl *FriendD = TSI->getType()->getAsCXXRecordDecl()) + Complete = MethodsAndNestedClassesComplete(FriendD, MNCComplete); + else + Complete = false; + } else { + // Friend functions are available through the NamedDecl of FriendDecl. + if (const FunctionDecl *FD = + dyn_cast<FunctionDecl>((*I)->getFriendDecl())) + Complete = FD->isDefined(); + else + // This is a template friend, give up. + Complete = false; + } + } + RecordsComplete[RD] = Complete; + return Complete; +} + +// END code copied from LLVM's clang/lib/Sema/Sema.cpp + +} + +bool PluginHandler::isAllRelevantCodeDefined(NamedDecl const * decl) { + switch (decl->getAccess()) { + case AS_protected: + if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) { + break; + } + LLVM_FALLTHROUGH; + case AS_private: + if (IsRecordFullyDefined( + cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete_, MNCComplete_)) + { + return true; + } + break; + default: + break; + } + return !decl->isExternallyVisible(); +} + std::unique_ptr<ASTConsumer> LibreOfficeAction::CreateASTConsumer( CompilerInstance& Compiler, StringRef ) { #if __cplusplus >= 201402L diff --git a/compilerplugins/clang/pluginhandler.hxx b/compilerplugins/clang/pluginhandler.hxx index 10b4ae21df2b..54ab613b3a7e 100644 --- a/compilerplugins/clang/pluginhandler.hxx +++ b/compilerplugins/clang/pluginhandler.hxx @@ -40,6 +40,9 @@ namespace loplugin class Plugin; struct InstantiationData; +// Used internally by PluginHandler::isAllRelevantCodeDefined and its (free) helper functions: +typedef llvm::DenseMap<const CXXRecordDecl*, bool> RecordCompleteMap; + /** Class that manages all LO modules. */ @@ -62,6 +65,10 @@ public: bool checkOverlap(SourceRange range); void addSourceModification(SourceRange range); StringRef const& getMainFileName() const { return mainFileName; } + + // Is all code that could see `decl` defined in this TU? + bool isAllRelevantCodeDefined(NamedDecl const * decl); + private: void handleOption( const std::string& option ); void createPlugins( std::set< std::string > rewriters ); @@ -76,6 +83,10 @@ private: bool warningsAsErrors; bool debugMode = false; std::vector<std::pair<char const*, char const*>> mvModifiedRanges; + + // Used internally by isAllRelevantCodeDefined: + RecordCompleteMap RecordsComplete_; + RecordCompleteMap MNCComplete_; }; /** diff --git a/compilerplugins/clang/test/unusedmember.cxx b/compilerplugins/clang/test/unusedmember.cxx new file mode 100644 index 000000000000..84802414f47b --- /dev/null +++ b/compilerplugins/clang/test/unusedmember.cxx @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace Enum +{ +namespace +{ +struct S +{ + enum E + { + E1, + E2 + }; + E e; +}; +} +void f(S s) { (void)s.e; } +} + +namespace ElaboratedEnum +{ +namespace +{ +struct S +{ + S() + { + enum E1 e1 = E11; + (void)e1; + } + enum E1 + { + E11, + E12 + }; + enum E2 + { + E21, + E22 + }; + enum E2 e2; +}; +} +void f() +{ + S s; + (void)s; + (void)s.e2; +} +} + +namespace UnusedEnum +{ +namespace +{ +struct S +{ + enum E // expected-error {{unused class member [loplugin:unusedmember]}} + { + E1, + E2 + }; +}; +} +void f() { (void)S::E1; } +} + +namespace UnusedDataMember +{ +namespace +{ +struct NT +{ + NT(int = 0) {} + ~NT() {} +}; +struct __attribute__((warn_unused)) T +{ + T(int = 0) {} + ~T() {} +}; +struct S +{ + int i1; + int i2; // expected-error {{unused class member [loplugin:unusedmember]}} + int const& i3; // expected-error {{unused class member [loplugin:unusedmember]}} + NT nt; + T t1; + T t2; // expected-error {{unused class member [loplugin:unusedmember]}} + T const& t3; // expected-error {{unused class member [loplugin:unusedmember]}} + S() + : i1(0) + , i3(i1) + , t1(0) + , t3(t1) + { + (void)i1; + (void)t1; + } +}; +} +void f() +{ + S s; + (void)s; +} +} + +namespace Alignof +{ +namespace +{ +struct S +{ + int i; +}; +} +void f() { (void)alignof(S const(&)[][10]); } +} + +namespace Aligned +{ +namespace +{ +struct S1 +{ + int i; +}; +struct S2 +{ + int i __attribute__((aligned(__alignof__(S1)))); +}; +} +void f() +{ + S2 s; + s.i = 0; +} +} + +int main() +{ + (void)&Enum::f; + (void)&ElaboratedEnum::f; + (void)&UnusedEnum::f; + (void)&UnusedDataMember::f; + (void)&Alignof::f; + (void)&Aligned::f; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/compilerplugins/clang/unusedmember.cxx b/compilerplugins/clang/unusedmember.cxx new file mode 100644 index 000000000000..f675de42dcd3 --- /dev/null +++ b/compilerplugins/clang/unusedmember.cxx @@ -0,0 +1,416 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// A more agressive check for unused C struct/C++ class members than what plain Clang offers. On +// the one hand, unlike -Wunused-private-field, it warns about all members regardless of access +// specifiers, if all code that can use a class has been seen. On the other hand, it warns about +// all kinds of members. But it uses some heuristics (the type showing up in sizeof, alignof, +// offsetof, certain casts) to determine that seemingly unused data members are probably used after +// all; the used heuristics were enough to not require any explicit [[maybe_unused]] decorations +// across the exisiting code base. + +#ifndef LO_CLANG_SHARED_PLUGINS + +#include <cassert> +#include <set> + +#include "config_clang.h" + +#include "check.hxx" +#include "plugin.hxx" + +namespace +{ +// Whether the CXXRecordDecl itself or one of its enclosing classes is a template: +bool isTemplated(CXXRecordDecl const* decl) +{ + if (decl->getDescribedClassTemplate() != nullptr) + { + return true; + } + if (auto const d = dyn_cast<CXXRecordDecl>(decl->getParent())) + { + return isTemplated(d); + } + return false; +} + +bool isWarnUnusedType(QualType type) +{ + if (auto const t = type->getAs<RecordType>()) + { + if (t->getDecl()->hasAttr<WarnUnusedAttr>()) + { + return true; + } + } + return loplugin::isExtraWarnUnusedType(type); +} + +class UnusedMember final : public loplugin::FilteringPlugin<UnusedMember> +{ +public: + explicit UnusedMember(loplugin::InstantiationData const& data) + : FilteringPlugin(data) + { + } + +#if CLANG_VERSION < 60000 + + bool TraverseAlignedAttr(AlignedAttr* attr) + { + bool ret = FilteringPlugin::TraverseAlignedAttr(attr); + PostTraverseAlignedAttr(attr, ret); + return ret; + } + + bool PostTraverseAlignedAttr(AlignedAttr* attr, bool run) + { + if (!run) + { + return false; + } + if (attr->isAlignmentExpr()) + { + if (!TraverseStmt(attr->getAlignmentExpr())) + { + return false; + } + } + else if (auto const tsi = attr->getAlignmentType()) + { + if (!TraverseTypeLoc(tsi->getTypeLoc())) + { + return false; + } + } + return true; + } + +#endif + + bool VisitCXXRecordDecl(CXXRecordDecl const* decl) //TODO: non-CXX RecordDecl? + { + if (ignoreLocation(decl)) + { + return true; + } + if (!decl->isThisDeclarationADefinition()) + { + return true; + } + if (!handler.isAllRelevantCodeDefined(decl)) + { + return true; + } + if (!compiler.getSourceManager().isInMainFile(decl->getLocation())) + { + // include/rtl/instance.hxx declares entities in an unnamed namespace + return true; + } + if (isTemplated(decl) || isa<ClassTemplatePartialSpecializationDecl>(decl)) + { + return true; + } + if (decl->isUnion() && decl->getIdentifier() == nullptr) + { + return true; //TODO + } + for (auto i = decl->decls_begin(); i != decl->decls_end(); ++i) + { + auto const d = *i; + if (d->isImplicit() || isa<AccessSpecDecl>(d) || isa<UsingDecl>(d)) + { + //TODO: only filter out UsingDecls that are actually used (if only to silence + // -Woverloaded-virtual) + continue; + } + if (isa<ClassTemplateDecl>(d) || isa<FunctionTemplateDecl>(d)) + { + //TODO: only filter out ones that are not instantiated at all + continue; + } + if (auto const d1 = dyn_cast<FriendDecl>(d)) + { + //TODO: determine whether the friendship is acutally required + auto const d2 = d1->getFriendDecl(); + if (d2 == nullptr) + { // happens for "friend class C;" + continue; + } + if (auto const d3 = dyn_cast<FunctionDecl>(d2)) + { +#if 0 //TODO: friend function definitions are not marked as referenced even if used? + if (!d3->isThisDeclarationADefinition()) //TODO: do this check for all kinds? +#endif + { + continue; + } + } + } + if (d->isReferenced()) + { + continue; + } + if (d->hasAttr<UnusedAttr>()) + { + continue; + } + // Check individual members instead of the whole CXXRecordDecl for comming from a macro, + // as CppUnit's CPPUNIT_TEST_SUITE_END (cppunit/extensions/HelperMacros.h) contains a + // partial member list ending in + // + // private: /* dummy typedef so that the macro can still end with ';'*/ + // typedef int CppUnitDummyTypedefForSemiColonEnding__ + // + if (compiler.getSourceManager().isMacroBodyExpansion(d->getLocation())) + { + return true; + } + if (auto const d1 = dyn_cast<FieldDecl>(d)) + { + if (d1->isUnnamedBitfield()) + { + continue; + } + if (!isWarnWhenUnusedType(d1->getType())) + { + continue; + } + deferred_.insert(d1); + continue; + } + if (auto const d1 = dyn_cast<FunctionDecl>(d)) + { + if (d1->isDeletedAsWritten()) // TODO: just isDeleted? + { + continue; + } + if (d1->isExplicitlyDefaulted()) + { + continue; + } + } + else if (auto const d2 = dyn_cast<TagDecl>(d)) + { + if (d2->getIdentifier() == nullptr) + { + continue; + } + if (isa<EnumDecl>(d2)) + { + deferred_.insert(d2); + continue; + } + } + else if (auto const d3 = dyn_cast<TypedefNameDecl>(d)) + { + // Some types, like (specializations of) std::iterator_traits, have specific + // requirements on their members; only covers std::iterator_traits for now (TODO: + // check that at least some member is actually used) + // (isa<ClassTemplatePartialSpecializationDecl>(decl) is already filtered out + // above): + if (isa<ClassTemplateSpecializationDecl>(decl) + && loplugin::DeclCheck(decl).Struct("iterator_traits").StdNamespace()) + { + auto const id = d3->getIdentifier(); + assert(id != nullptr); + auto const n = id->getName(); + if (n == "difference_type" || n == "iterator_category" || n == "pointer" + || n == "reference" || n == "value_type") + { + continue; + } + } + } + report(DiagnosticsEngine::Warning, "unused class member", d->getLocation()) + << d->getSourceRange(); + } + return true; + } + + bool VisitOffsetOfExpr(OffsetOfExpr const* expr) + { + if (ignoreLocation(expr)) + { + return true; + } + layout_.insert(expr->getTypeSourceInfo() + ->getType() + ->castAs<RecordType>() + ->getDecl() + ->getCanonicalDecl()); + return true; + } + + bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const* expr) + { + if (ignoreLocation(expr)) + { + return true; + } + switch (expr->getKind()) + { + case UETT_SizeOf: + case UETT_AlignOf: +#if CLANG_VERSION >= 80000 + case UETT_PreferredAlignOf: +#endif + break; + default: + return true; + } + if (!expr->isArgumentType()) + { + return true; + } + auto t = expr->getArgumentType(); + if (auto const t1 = t->getAs<ReferenceType>()) + { + t = t1->getPointeeType(); + } + if (auto const t1 = t->getAsArrayTypeUnsafe()) + { + t = compiler.getASTContext().getBaseElementType(t1); + } + if (auto const t1 = t->getAs<RecordType>()) + { + layout_.insert(t1->getDecl()->getCanonicalDecl()); + } + return true; + } + + // Handling implicit, C-style, static and reinterpret casts between void* and record types + // (though reinterpret_cast would be ruled out by loplugin:redundantcast): + bool VisitCastExpr(CastExpr const* expr) + { + if (ignoreLocation(expr)) + { + return true; + } + auto const t1 = expr->getType(); + auto const t2 = expr->getSubExprAsWritten()->getType(); + if (loplugin::TypeCheck(t1).Pointer().Void()) + { + recordCastedRecordDecl(t2); + } + else if (loplugin::TypeCheck(t2).Pointer().Void()) + { + recordCastedRecordDecl(t1); + } + return true; + } + + bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr const* expr) + { + if (ignoreLocation(expr)) + { + return true; + } + recordCastedRecordDecl(expr->getTypeAsWritten()); + recordCastedRecordDecl(expr->getSubExprAsWritten()->getType()); + return true; + } + + bool VisitElaboratedTypeLoc(ElaboratedTypeLoc tloc) + { + if (ignoreLocation(tloc)) + { + return true; + } + auto const tl = tloc.getNamedTypeLoc().getAs<TagTypeLoc>(); + if (tl.isNull()) + { + return true; + } + if (tl.isDefinition()) + { + return true; + } + if (auto const d = dyn_cast<EnumDecl>(tl.getDecl())) + { + // For some reason, using an elaborated type specifier in (at least) a FieldDecl, as in + // + // enum E { ... }; + // enum E e; + // + // doesn't cause the EnumDecl to be marked as referenced. (This should fix it, but note + // the warning at <https://github.com/llvm/llvm-project/commit/ + // b96ec568715450106b4f1dd4a20c1c14e9bca6c4#diff-019094457f96a6ed0ee072731d447073R396>: + // "[...] a type written 'struct foo' should be represented as an ElaboratedTypeLoc. We + // currently only do that when C++ is enabled [...]" + deferred_.erase(d->getCanonicalDecl()); + } + return true; + } + + void postRun() override + { + for (auto const d : deferred_) + { + if (auto const d1 = dyn_cast<FieldDecl>(d)) + { + if (layout_.find(d1->getParent()->getCanonicalDecl()) != layout_.end()) + { + continue; + } + } + report(DiagnosticsEngine::Warning, "unused class member", d->getLocation()) + << d->getSourceRange(); + } + } + +private: + void run() override + { + if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) + { + postRun(); + } + } + + bool isWarnWhenUnusedType(QualType type) + { + auto t = type; + if (auto const t1 = t->getAs<ReferenceType>()) + { + t = t1->getPointeeType(); + } + return t.isTrivialType(compiler.getASTContext()) || isWarnUnusedType(t); + } + + void recordCastedRecordDecl(QualType type) + { + for (auto t = type;;) + { + if (auto const t1 = t->getAs<clang::PointerType>()) + { + t = t1->getPointeeType(); + continue; + } + if (auto const t1 = t->getAs<RecordType>()) + { + layout_.insert(t1->getDecl()->getCanonicalDecl()); + } + break; + } + } + + // RecordDecls whose layout (i.e., contained FieldDecls) must presumably not be changed: + std::set<TagDecl const*> layout_; + + std::set<Decl const*> deferred_; +}; + +loplugin::Plugin::Registration<UnusedMember> unusedmember("unusedmember"); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/solenv/CompilerTest_compilerplugins_clang.mk b/solenv/CompilerTest_compilerplugins_clang.mk index 3c9969bd628f..a65969a0078e 100644 --- a/solenv/CompilerTest_compilerplugins_clang.mk +++ b/solenv/CompilerTest_compilerplugins_clang.mk @@ -95,6 +95,7 @@ $(eval $(call gb_CompilerTest_add_exception_objects,compilerplugins_clang, \ compilerplugins/clang/test/unusedenumconstants \ compilerplugins/clang/test/unusedfields \ compilerplugins/clang/test/unusedindex \ + compilerplugins/clang/test/unusedmember \ compilerplugins/clang/test/unusedvariablecheck \ compilerplugins/clang/test/unusedvariablemore \ compilerplugins/clang/test/useuniqueptr \ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits