[Libreoffice-commits] core.git: compilerplugins/clang solenv/CompilerTest_compilerplugins_clang.mk

Stephan Bergmann (via logerrit) logerrit at kemper.freedesktop.org
Thu Dec 5 12:32:02 UTC 2019


 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 <sbergman at redhat.com>
AuthorDate: Wed Dec 4 14:32:15 2019 +0100
Commit:     Stephan Bergmann <sbergman at 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 <sbergman at 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 \


More information about the Libreoffice-commits mailing list