]> granicus.if.org Git - clang/commitdiff
Introduce -Wunused-private-field. If enabled, this warning detects
authorDaniel Jasper <djasper@google.com>
Wed, 6 Jun 2012 08:32:04 +0000 (08:32 +0000)
committerDaniel Jasper <djasper@google.com>
Wed, 6 Jun 2012 08:32:04 +0000 (08:32 +0000)
unused private fields of classes that are fully defined in the current
translation unit.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@158054 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Basic/DiagnosticGroups.td
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Sema/Sema.h
lib/Sema/Sema.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaExprMember.cpp
test/SemaCXX/warn-unused-member.cpp [new file with mode: 0644]

index f48eee416a9266d48391247bd7278b5258034606..fdafb9910982743b355508be727207a879617f94 100644 (file)
@@ -230,6 +230,7 @@ def UnusedComparison : DiagGroup<"unused-comparison">;
 def UnusedExceptionParameter : DiagGroup<"unused-exception-parameter">;
 def UnneededInternalDecl : DiagGroup<"unneeded-internal-declaration">;
 def UnneededMemberFunction : DiagGroup<"unneeded-member-function">;
+def UnusedPrivateField : DiagGroup<"unused-private-field">;
 def UnusedFunction : DiagGroup<"unused-function", [UnneededInternalDecl]>;
 def UnusedMemberFunction : DiagGroup<"unused-member-function",
                                      [UnneededMemberFunction]>;
@@ -311,6 +312,7 @@ def Unused : DiagGroup<"unused",
                        [UnusedArgument, UnusedFunction, UnusedLabel,
                         // UnusedParameter, (matches GCC's behavior)
                         // UnusedMemberFunction, (clean-up llvm before enabling)
+                        // UnusedPrivateField, (clean-up llvm before enabling)
                         UnusedValue, UnusedVariable]>,
                         DiagCategory<"Unused Entity Issue">;
 
index 7ed00e6b52b904e8b965370dc47e1f760becc6da..3dc03fa802105fe7a34eccaecf85454db1a61c97 100644 (file)
@@ -173,6 +173,8 @@ def warn_unneeded_internal_decl : Warning<
 def warn_unneeded_member_function : Warning<
   "member function %0 is not needed and will not be emitted">,
   InGroup<UnneededMemberFunction>, DefaultIgnore;
+def warn_unused_private_field: Warning<"private field %0 is not used">,
+  InGroup<UnusedPrivateField>, DefaultIgnore;
 
 def warn_parameter_size: Warning<
   "%0 is a large (%1 bytes) pass-by-value argument; "
index d7bc2bfe19b71582b1988b89d455d78b90f0b814..f0d3213bd3e263e155fd75923575753f25ce1b88 100644 (file)
@@ -37,6 +37,7 @@
 #include "clang/Basic/ExpressionTraits.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/OwningPtr.h"
+#include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include <deque>
@@ -267,6 +268,11 @@ public:
   /// FieldCollector - Collects CXXFieldDecls during parsing of C++ classes.
   OwningPtr<CXXFieldCollector> FieldCollector;
 
+  typedef llvm::SmallSetVector<const NamedDecl*, 16> NamedDeclSetType;
+
+  /// \brief Set containing all declared private fields that are not used.
+  NamedDeclSetType UnusedPrivateFields;
+
   typedef llvm::SmallPtrSet<const CXXRecordDecl*, 8> RecordDeclSetTy;
 
   /// PureVirtualClassDiagSet - a set of class declarations which we have
index 9f2880fb4e18a5bb8659d17863faaa4456606641..e0d405d175f973d9d5710885d3efa9138415cd64 100644 (file)
@@ -30,6 +30,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTDiagnostic.h"
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclFriend.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
@@ -422,6 +423,79 @@ void Sema::LoadExternalWeakUndeclaredIdentifiers() {
   }
 }
 
+
+typedef llvm::DenseMap<const CXXRecordDecl*, bool> RecordCompleteMap;
+
+/// \brief 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->isPure() && !isa<CXXDestructorDecl>(M));
+    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;
+}
+
+/// \brief 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;
+}
+
 /// ActOnEndOfTranslationUnit - This is called at the very end of the
 /// translation unit when EOF is reached and all but the top-level scope is
 /// popped.
@@ -628,6 +702,23 @@ void Sema::ActOnEndOfTranslationUnit() {
     checkUndefinedInternals(*this);
   }
 
+  if (Diags.getDiagnosticLevel(diag::warn_unused_private_field,
+                               SourceLocation())
+        != DiagnosticsEngine::Ignored) {
+    RecordCompleteMap RecordsComplete;
+    RecordCompleteMap MNCComplete;
+    for (NamedDeclSetType::iterator I = UnusedPrivateFields.begin(),
+         E = UnusedPrivateFields.end(); I != E; ++I) {
+      const NamedDecl *D = *I;
+      const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(D->getDeclContext());
+      if (RD && !RD->isUnion() &&
+          IsRecordFullyDefined(RD, RecordsComplete, MNCComplete)) {
+        Diag(D->getLocation(), diag::warn_unused_private_field)
+              << D->getDeclName();
+      }
+    }
+  }
+
   // Check we've noticed that we're no longer parsing the initializer for every
   // variable. If we miss cases, then at best we have a performance issue and
   // at worst a rejects-valid bug.
index 676063ac0840cc07f085dc4cbc3c821e9043d26e..e7a22165daca3e95b37e3f8a3104dd780da3fb91 100644 (file)
@@ -1439,6 +1439,17 @@ bool Sema::CheckIfOverriddenFunctionIsMarkedFinal(const CXXMethodDecl *New,
   return true;
 }
 
+static bool InitializationHasSideEffects(const FieldDecl &FD) {
+  if (!FD.getType().isNull()) {
+    if (const CXXRecordDecl *RD = FD.getType()->getAsCXXRecordDecl()) {
+      return !RD->isCompleteDefinition() ||
+             !RD->hasTrivialDefaultConstructor() ||
+             !RD->hasTrivialDestructor();
+    }
+  }
+  return false;
+}
+
 /// ActOnCXXMemberDeclarator - This is invoked when a C++ class member
 /// declarator is parsed. 'AS' is the access specifier, 'BW' specifies the
 /// bitfield width if there is one, 'InitExpr' specifies the initializer if
@@ -1624,8 +1635,23 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
 
   assert((Name || isInstField) && "No identifier for non-field ?");
 
-  if (isInstField)
-    FieldCollector->Add(cast<FieldDecl>(Member));
+  if (isInstField) {
+    FieldDecl *FD = cast<FieldDecl>(Member);
+    FieldCollector->Add(FD);
+
+    if (Diags.getDiagnosticLevel(diag::warn_unused_private_field,
+                                 FD->getLocation())
+          != DiagnosticsEngine::Ignored) {
+      // Remember all explicit private FieldDecls that have a name, no side
+      // effects and are not part of a dependent type declaration.
+      if (!FD->isImplicit() && FD->getDeclName() &&
+          FD->getAccess() == AS_private &&
+          !FD->getParent()->getTypeForDecl()->isDependentType() &&
+          !InitializationHasSideEffects(*FD))
+        UnusedPrivateFields.insert(FD);
+    }
+  }
+
   return Member;
 }
 
@@ -2105,6 +2131,25 @@ Sema::BuildMemberInitializer(ValueDecl *Member, Expr *Init,
     Args = InitList->getInits();
     NumArgs = InitList->getNumInits();
   }
+
+  // Mark FieldDecl as being used if it is a non-primitive type and the
+  // initializer does not call the default constructor (which is trivial
+  // for all entries in UnusedPrivateFields).
+  // FIXME: Make this smarter once more side effect-free types can be
+  // determined.
+  if (NumArgs > 0) {
+    if (Member->getType()->isRecordType()) {
+      UnusedPrivateFields.remove(Member);
+    } else {
+      for (unsigned i = 0; i < NumArgs; ++i) {
+        if (Args[i]->HasSideEffects(Context)) {
+          UnusedPrivateFields.remove(Member);
+          break;
+        }
+      }
+    }
+  }
+
   for (unsigned i = 0; i < NumArgs; ++i) {
     SourceLocation L;
     if (InitExprContainsUninitializedFields(Args[i], Member, &L)) {
@@ -2808,6 +2853,13 @@ static bool CollectFieldInitializer(Sema &SemaRef, BaseAndFieldInfo &Info,
                                                       SourceLocation(), 0,
                                                       SourceLocation());
     Info.AllToInit.push_back(Init);
+
+    // Check whether this initializer makes the field "used".
+    Expr *InitExpr = Field->getInClassInitializer();
+    if (Field->getType()->isRecordType() ||
+        (InitExpr && InitExpr->HasSideEffects(SemaRef.Context)))
+      SemaRef.UnusedPrivateFields.remove(Field);
+
     return false;
   }
 
index f8bf908dfc71b048cbfca926e1e5ff5f269e7602..d888ab90b67f18b036df8787402f892f830b92d4 100644 (file)
@@ -1591,6 +1591,8 @@ BuildFieldReferenceExpr(Sema &S, Expr *BaseExpr, bool IsArrow,
       MemberType = S.Context.getQualifiedType(MemberType, Combined);
   }
   
+  S.UnusedPrivateFields.remove(Field);
+
   ExprResult Base =
   S.PerformObjectMemberConversion(BaseExpr, SS.getScopeRep(),
                                   FoundDecl, Field);
diff --git a/test/SemaCXX/warn-unused-member.cpp b/test/SemaCXX/warn-unused-member.cpp
new file mode 100644 (file)
index 0000000..16e1e22
--- /dev/null
@@ -0,0 +1,193 @@
+// RUN: %clang_cc1 -fsyntax-only -Wunused-private-field -verify -std=c++11 %s
+
+class NotFullyDefined {
+ public:
+  NotFullyDefined();
+ private:
+  int y;
+};
+
+class HasUndefinedNestedClass {
+  class Undefined;
+  int unused_;
+};
+
+class HasUndefinedPureVirtualDestructor {
+  virtual ~HasUndefinedPureVirtualDestructor() = 0;
+  int unused_;
+};
+
+class HasDefinedNestedClasses {
+  class DefinedHere {};
+  class DefinedOutside;
+  int unused_; // expected-warning{{private field 'unused_' is not used}}
+};
+class HasDefinedNestedClasses::DefinedOutside {};
+
+class HasUndefinedFriendFunction {
+  friend void undefinedFriendFunction();
+  int unused_;
+};
+
+class HasUndefinedFriendClass {
+  friend class NotFullyDefined;
+  friend class NotDefined;
+  int unused_;
+};
+
+class HasFriend {
+  friend class FriendClass;
+  friend void friendFunction(HasFriend f);
+  int unused_; // expected-warning{{private field 'unused_' is not used}}
+  int used_by_friend_class_;
+  int used_by_friend_function_;
+};
+
+class ClassWithTemplateFriend {
+  template <typename T> friend class TemplateFriend;
+  int used_by_friend_;
+  int unused_;
+};
+
+template <typename T> class TemplateFriend {
+public:
+  TemplateFriend(ClassWithTemplateFriend my_friend) {
+    int var = my_friend.used_by_friend_;
+  }
+};
+
+class FriendClass {
+  HasFriend my_friend_;
+  void use() {
+    my_friend_.used_by_friend_class_ = 42;
+  }
+};
+
+void friendFunction(HasFriend my_friend) {
+  my_friend.used_by_friend_function_ = 42;
+}
+
+class NonTrivialConstructor {
+ public:
+  NonTrivialConstructor() {}
+};
+
+class NonTrivialDestructor {
+ public:
+  ~NonTrivialDestructor() {}
+};
+
+class Trivial {
+ public:
+  Trivial() = default;
+  Trivial(int a) {}
+};
+
+int side_effect() {
+  return 42;
+}
+
+class A {
+ public:
+  A() : primitive_type_(42), default_initializer_(), other_initializer_(42),
+        trivial_(), user_constructor_(42),
+        initialized_with_side_effect_(side_effect()) {
+    used_ = 42;
+  }
+
+  A(int x, A* a) : pointer_(a) {}
+
+ private:
+  int primitive_type_; // expected-warning{{private field 'primitive_type_' is not used}}
+  A* pointer_; // expected-warning{{private field 'pointer_' is not used}}
+  int no_initializer_; // expected-warning{{private field 'no_initializer_' is not used}}
+  int default_initializer_; // expected-warning{{private field 'default_initializer_' is not used}}
+  int other_initializer_; // expected-warning{{private field 'other_initializer_' is not used}}
+  int used_, unused_; // expected-warning{{private field 'unused_' is not used}}
+  int in_class_initializer_ = 42; // expected-warning{{private field 'in_class_initializer_' is not used}}
+  int in_class_initializer_with_side_effect_ = side_effect();
+  Trivial trivial_initializer_ = Trivial();
+  Trivial non_trivial_initializer_ = Trivial(42);
+  int initialized_with_side_effect_;
+  static int static_fields_are_ignored_;
+
+  Trivial trivial_; // expected-warning{{private field 'trivial_' is not used}}
+  Trivial user_constructor_;
+  NonTrivialConstructor non_trivial_constructor_;
+  NonTrivialDestructor non_trivial_destructor_;
+};
+
+class EverythingUsed {
+ public:
+  EverythingUsed() : as_array_index_(0), var_(by_initializer_) {
+    var_ = sizeof(sizeof_);
+    int *use = &by_reference_;
+    int test[2];
+    test[as_array_index_] = 42;
+  }
+
+  template<class T>
+  void useStuff(T t) {
+    by_template_function_ = 42;
+  }
+
+ private:
+  int var_;
+  int sizeof_;
+  int by_reference_;
+  int by_template_function_;
+  int as_array_index_;
+  int by_initializer_;
+};
+
+namespace mutual_friends {
+// Undefined methods make mutual friends undefined.
+class A {
+  int a;
+  friend class B;
+  void doSomethingToAOrB();
+};
+class B {
+  int b;
+  friend class A;
+};
+
+// Undefined friends do not make a mutual friend undefined.
+class C {
+  int c;
+  void doSomethingElse() {}
+  friend class E;
+  friend class D;
+};
+class D {
+  int d; // expected-warning{{private field 'd' is not used}}
+  friend class C;
+};
+
+// Undefined nested classes make mutual friends undefined.
+class F {
+  int f;
+  class G;
+  friend class H;
+};
+class H {
+  int h;
+  friend class F;
+};
+}  // namespace mutual_friends
+
+namespace anonymous_structs_unions {
+class A {
+ private:
+  // FIXME: Look at the DeclContext for anonymous structs/unions.
+  union {
+    int *Aligner;
+    unsigned char Data[8];
+  };
+};
+union S {
+ private:
+  int *Aligner;
+  unsigned char Data[8];
+};
+}  // namespace anonymous_structs_unions