Add a static analysis to ensure the correct implementation of Cycle Collection methods
authorMichael Layzell <michael@thelayzells.com>
Wed, 21 Oct 2015 13:55:17 -0400
changeset 621176 e49ec26133aaa73fa65b5f0a156b7a569b21f865
parent 599832 efaa4e8a0c9c9c6be48363c11580c4e4f953e2c1
child 621177 4dd71a68017d7cad956773a721ac1b2e1066cba5
push id95882
push usermichael@thelayzells.com
push dateWed, 04 Nov 2015 04:57:04 +0000
treeherdertry@870a8f5c9195 [default view] [failures only]
milestone44.0a1
Add a static analysis to ensure the correct implementation of Cycle Collection methods
build/clang-plugin/clang-plugin.cpp
build/clang-plugin/tests/TestCycleCollection.cpp
build/clang-plugin/tests/TestMozCCXPCOMBase.cpp
build/clang-plugin/tests/moz.build
mfbt/Attributes.h
mfbt/EnumeratedArray.h
mfbt/RefPtr.h
mfbt/nsRefPtr.h
xpcom/glue/nsCOMArray.h
xpcom/glue/nsCOMPtr.h
xpcom/glue/nsCycleCollectionParticipant.h
xpcom/glue/nsTArray.h
xpcom/glue/nsTHashtable.h
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -1,15 +1,16 @@
 /* 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/. */
 
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/CXXInheritance.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/Basic/Version.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendPluginRegistry.h"
 #include "clang/Frontend/MultiplexConsumer.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/DenseMap.h"
@@ -98,29 +99,41 @@ private:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class NoExplicitMoveConstructorChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
+  class CycleCollectionChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
+  class MozCCXPCOMBaseChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   ScopeChecker scopeChecker;
   ArithmeticArgChecker arithmeticArgChecker;
   TrivialCtorDtorChecker trivialCtorDtorChecker;
   NaNExprChecker nanExprChecker;
   NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
   RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
   ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
   NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker;
   NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
   NonMemMovableChecker nonMemMovableChecker;
   ExplicitImplicitChecker explicitImplicitChecker;
   NoAutoTypeChecker noAutoTypeChecker;
   NoExplicitMoveConstructorChecker noExplicitMoveConstructorChecker;
+  CycleCollectionChecker cycleCollectionChecker;
+  MozCCXPCOMBaseChecker mozCCXPCOMBaseChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
 std::string getDeclarationNamespace(const Decl *decl) {
   const DeclContext *DC =
       decl->getDeclContext()->getEnclosingNamespaceContext();
@@ -471,16 +484,66 @@ bool isClassRefCounted(const CXXRecordDe
 
 bool isClassRefCounted(QualType T) {
   while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
     T = arrTy->getElementType();
   CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
   return clazz ? isClassRefCounted(clazz) : false;
 }
 
+bool isClassNsISupports(const CXXRecordDecl *D) {
+  return D->getName() == "nsISupports";
+}
+
+bool isClassNsISupports(const CXXBaseSpecifier *Specifier,
+                        CXXBasePath &Path,
+                        void *UserData = nullptr) {
+  QualType T = Specifier->getType();
+  if (!T.isNull()) {
+    if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) {
+      return isClassNsISupports(Rec);
+    }
+  }
+
+  return false;
+}
+
+bool isClassXPCOM(const CXXRecordDecl *D) {
+  CXXBasePaths Paths(false, false, false);
+  // lookupInBases' semantics changed in version 308
+#if CLANG_VERSION_FULL >= 308
+  return D->lookupInBases(isClassNsISupports, Paths);
+#else
+  return D->lookupInBases(isClassNsISupports, nullptr, Paths);
+#endif
+}
+
+bool isClassXPCOM(QualType T) {
+  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? isClassXPCOM(clazz) : false;
+}
+
+// A class is a native cycle-collectable class if it has a inner class
+// declaration with the name `cycleCollection`.
+bool isClassNativeCC(const CXXRecordDecl *D) {
+  for (const Decl *Candidate : D->decls()) {
+    const CXXRecordDecl *Rec = dyn_cast<CXXRecordDecl>(Candidate);
+    if (Rec && Rec->getName() == "cycleCollection") {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool isClassNativeCC(QualType T) {
+  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? isClassNativeCC(clazz) : false;
+}
+
 template <class T> bool IsInSystemHeader(const ASTContext &AC, const T &D) {
   auto &SourceManager = AC.getSourceManager();
   auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
   if (ExpansionLoc.isInvalid()) {
     return false;
   }
   return SourceManager.isInSystemHeader(ExpansionLoc);
 }
@@ -667,16 +730,28 @@ AST_MATCHER(QualType, autoNonAutoableTyp
     }
   }
   return false;
 }
 
 AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) {
   return Node.isExplicit() && Node.isMoveConstructor();
 }
+
+AST_MATCHER(FunctionDecl, isCCUnlinkImpl) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_cc_unlink_impl");
+}
+
+AST_MATCHER(FunctionDecl, isCCTraverseImpl) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_cc_traverse_impl");
+}
+
+AST_MATCHER(CXXRecordDecl, isNativeCC) {
+  return isClassNativeCC(&Node);
+}
 }
 }
 
 namespace {
 
 void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag,
                                                 QualType T,
                                                 SourceLocation Loc) {
@@ -722,22 +797,27 @@ void CustomTypeAnnotation::dumpAnnotatio
       return;
     }
 
     T = Reason.Type;
     Reason = directAnnotationReason(T);
   }
 }
 
-bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const {
+const TagDecl *getAsTagDecl(const Type* T) {
 #if CLANG_VERSION_FULL >= 306
-  if (const TagDecl *D = T->getAsTagDecl()) {
+  return T->getAsTagDecl();
 #else
-  if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) {
+  // XXX FIXME: This should be a polyfill rather than only working for CXXRecordDecls
+  return T->getAsCXXRecordDecl();
 #endif
+}
+
+bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const {
+  if (const TagDecl *D = getAsTagDecl(&*T)) {
     return MozChecker::hasCustomAnnotation(D, Spelling);
   }
   return false;
 }
 
 CustomTypeAnnotation::AnnotationReason
 CustomTypeAnnotation::directAnnotationReason(QualType T) {
   if (hasLiteralAnnotation(T)) {
@@ -945,16 +1025,24 @@ DiagnosticsMatcher::DiagnosticsMatcher()
           .bind("ctor"),
       &explicitImplicitChecker);
 
   astMatcher.addMatcher(varDecl(hasType(autoNonAutoableType())).bind("node"),
                         &noAutoTypeChecker);
 
   astMatcher.addMatcher(constructorDecl(isExplicitMoveConstructor()).bind("node"),
                         &noExplicitMoveConstructorChecker);
+
+  astMatcher.addMatcher(recordDecl(isNativeCC()).bind("class"),
+                        &mozCCXPCOMBaseChecker);
+
+  astMatcher.addMatcher(methodDecl(isCCUnlinkImpl()).bind("unlink"),
+                        &cycleCollectionChecker);
+  astMatcher.addMatcher(methodDecl(isCCTraverseImpl()).bind("traverse"),
+                        &cycleCollectionChecker);
 }
 
 // These enum variants determine whether an allocation has occured in the code.
 enum AllocationVariety {
   AV_None,
   AV_Global,
   AV_Automatic,
   AV_Temporary,
@@ -1369,16 +1457,229 @@ void DiagnosticsMatcher::NoExplicitMoveC
   // Everything we needed to know was checked in the matcher - we just report
   // the error here
   const CXXConstructorDecl *D =
     Result.Nodes.getNodeAs<CXXConstructorDecl>("node");
 
   Diag.Report(D->getLocation(), ErrorID);
 }
 
+enum CycleCollectionFunctionType {
+  CCFT_Unlink,
+  CCFT_Traverse
+};
+
+// This is a helper method used for determining whether a particular type must be traversed
+bool mustTraverseType(DiagnosticsEngine &Diag, unsigned ErrorID, QualType T) {
+  if (auto Decl = T->getAsCXXRecordDecl()) {
+    if (MozChecker::hasCustomAnnotation(Decl, "moz_cc_traverse")) {
+      return true;
+    }
+
+    if (MozChecker::hasCustomAnnotation(Decl, "moz_cc_traversable_ptr")) {
+      if (auto Spec = dyn_cast<ClassTemplateSpecializationDecl>(Decl)) {
+        for (auto& Arg : Spec->getTemplateArgs().asArray()) {
+          if (Arg.getKind() == TemplateArgument::Type) {
+            QualType Type = Arg.getAsType();
+
+            auto ArgDecl = Type->getAsCXXRecordDecl();
+            if (!ArgDecl) {
+              continue;
+            }
+
+            if (isClassNativeCC(ArgDecl) ||
+                MozChecker::hasCustomAnnotation(ArgDecl, "moz_cc")) {
+              return true;
+            }
+          }
+        }
+      } else {
+        // XXX Report an error for unexpected moz_cc_traversable_ptr on non-template type
+      }
+    }
+
+    if (MozChecker::hasCustomAnnotation(Decl, "moz_cc_owns_template_arg")) {
+      if (auto Spec = dyn_cast<ClassTemplateSpecializationDecl>(Decl)) {
+        for (auto& Arg : Spec->getTemplateArgs().asArray()) {
+          if (Arg.getKind() == TemplateArgument::Type) {
+            QualType Type = Arg.getAsType();
+            if (mustTraverseType(Diag, ErrorID, Type)) {
+              return true;
+            }
+          }
+        }
+      } else {
+        // XXX Report an error for unexpected moz_cc_owns_template_arg on non-template type
+      }
+    }
+
+    for (const CXXBaseSpecifier &Base : Decl->bases()) {
+      if (mustTraverseType(Diag, ErrorID, Base.getType())) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+void CCCheckTraverseMembers(DiagnosticsEngine &Diag,
+                            const FunctionDecl *Func,
+                            CycleCollectionFunctionType Type) {
+  auto Compound = dyn_cast_or_null<CompoundStmt>(Func->getBody());
+  if (!Compound) {
+    return;
+  }
+
+  const char *CollectFuncName;
+  const char *TypeName;
+  uint32_t CollectFuncArgCount;
+  uint32_t CollectFuncArgNum;
+  switch (Type) {
+  case CCFT_Unlink:
+    CollectFuncName = "ImplCycleCollectionUnlink";
+    TypeName = "Unlink";
+    CollectFuncArgCount = 1;
+    CollectFuncArgNum = 0;
+    break;
+  case CCFT_Traverse:
+    CollectFuncName = "ImplCycleCollectionTraverse";
+    TypeName = "Traverse";
+    CollectFuncArgCount = 4;
+    CollectFuncArgNum = 1;
+    break;
+  }
+
+  // XXX FIXME This is super duper ugly, and almost certanly could be written more nicely
+  std::vector<ValueDecl*> Handled;
+  const CXXRecordDecl *Record = nullptr;
+  for (auto S : Compound->body()) {
+    // Discover the Record declaration for later
+    if (auto DS = dyn_cast<DeclStmt>(S)) {
+      for (auto Decl : DS->decls()) {
+        if (auto VarD = dyn_cast<VarDecl>(Decl)) {
+          if (VarD->getName() == "tmp") {
+            auto T = VarD->getType()->getPointeeType();
+            if (!T.isNull()) {
+              Record = T->getAsCXXRecordDecl();
+            }
+          }
+        }
+      }
+    }
+
+    // If we see a call to ImplCycleCollectionXXX, record that we handled the argument
+    if (auto Call = dyn_cast<CallExpr>(S)) {
+      auto Callee = Call->getDirectCallee();
+      if (Callee && Callee->getName() == CollectFuncName) {
+        if (Call->getNumArgs() != CollectFuncArgCount) {
+          unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+              DiagnosticIDs::Error, "Unexpected number of arguments to %0");
+          Diag.Report(Call->getLocStart(), ErrorID) << CollectFuncName;
+          continue;
+        }
+
+        if (auto Arg = dyn_cast<MemberExpr>(Call->getArg(CollectFuncArgNum)->IgnoreCasts())) {
+          auto Base = dyn_cast<DeclRefExpr>(Arg->getBase()->IgnoreCasts());
+          if (!Base || Base->getDecl()->getName() != "tmp") {
+            continue;
+          }
+
+          Handled.push_back(Arg->getMemberDecl());
+        }
+      } else if (Callee &&
+                 MozChecker::hasCustomAnnotation(Callee, "moz_cc_ignore_failures_function")) {
+        // We should stop checking, as we found a MOZ_CC_IGNORE_FAILURES call.
+        return;
+      }
+    }
+  }
+  if (!Record) {
+    return;
+  }
+
+  unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error,
+      "Traversable member %0 of class %2 not mentioned in %1 implementation");
+
+  // Get the members from Record which need to be Traversed / Unlinked
+  for (auto F : Record->fields()) {
+    auto T = F->getType();
+    if (mustTraverseType(Diag, ErrorID, T)) {
+      // Check if this field is inside the list, and error if it is not
+      bool Found = false;
+      for (auto D : Handled) {
+        if (F == D) {
+          Found = true;
+          break;
+        }
+      }
+
+      if (!Found) {
+        Diag.Report(F->getLocStart(), ErrorID) << F << TypeName << Record;
+      }
+    }
+  }
+}
+
+void DiagnosticsMatcher::CycleCollectionChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+
+  if (const FunctionDecl *Unlink = Result.Nodes.getNodeAs<FunctionDecl>("unlink")) {
+    CCCheckTraverseMembers(Diag, Unlink, CCFT_Unlink);
+  } else if (const FunctionDecl *Traverse = Result.Nodes.getNodeAs<FunctionDecl>("traverse")) {
+    CCCheckTraverseMembers(Diag, Traverse, CCFT_Traverse);
+  }
+}
+
+void EnsureBaseIsMozCC(DiagnosticsEngine &Diag,
+                       const CXXRecordDecl *R,
+                       const CXXRecordDecl *Reason) {
+  if (!isClassXPCOM(R) || isClassNativeCC(R)) {
+    return;
+  }
+
+  if (!MozChecker::hasCustomAnnotation(R, "moz_cc")) {
+    unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+        DiagnosticIDs::Error,
+        "Class %0 must be annotated with MOZ_CC (or [cyclecollect] in XPIDL)");
+    unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID(
+        DiagnosticIDs::Note,
+        "Class %0 is an XPCOM base class for the cycle-collected class %1");
+
+    Diag.Report(R->getLocStart(), ErrorID) << R;
+    Diag.Report(Reason->getLocStart(), NoteID) << R << Reason;
+  }
+
+  for (const CXXBaseSpecifier &Base : R->bases()) {
+    QualType T = Base.getType();
+    if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) {
+      EnsureBaseIsMozCC(Diag, D, Reason);
+    }
+  }
+}
+
+void DiagnosticsMatcher::MozCCXPCOMBaseChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  const CXXRecordDecl *R = Result.Nodes.getNodeAs<CXXRecordDecl>("class");
+
+  if (!isClassXPCOM(R)) {
+    return;
+  }
+
+  for (const CXXBaseSpecifier &Base : R->bases()) {
+    QualType T = Base.getType();
+    if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) {
+      EnsureBaseIsMozCC(Diag, D, R);
+    }
+  }
+}
+
 class MozCheckAction : public PluginASTAction {
 public:
   ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI,
                                    StringRef fileName) override {
 #if CLANG_VERSION_FULL >= 306
     std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI));
     ASTConsumerPtr other(checker->getOtherConsumer());
 
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestCycleCollection.cpp
@@ -0,0 +1,294 @@
+// Applies to XPCOM class declarations. Owning pointers to annotated types must be
+// traversed in Traverse and Unlink implementations.
+#define MOZ_CC __attribute__((annotate("moz_cc")))
+
+// The following annotations are "TRAVERSABLE" annotations. A type is traversable
+// if it is legal to call ImplCycleCollectionTraverse and ImplCycleCollectionUnlink
+// on them. This is distinct from the concept of Cycle Collectable.
+
+// Applies to templated class declarations. Annotates that the given class is a
+// traversable pointer. It is assumed that type template arguments of this class are
+// the pointee type.
+#define MOZ_CC_TRAVERSABLE_PTR __attribute__((annotate("moz_cc_traversable_ptr")))
+
+// Applies to templated class declarations. Annotates that the given type is
+// traversable if and only if at least one of its type template arguments are
+// also traversable.
+#define MOZ_CC_OWNS_TEMPLATE_ARG __attribute__((annotate("moz_cc_owns_template_arg")))
+
+// Applies to class declarations. Annotates that the given type is unconditionally
+// traversable.
+#define MOZ_CC_TRAVERSE __attribute__((annotate("moz_cc_traverse")))
+
+// These annotations are used to identify method declarations which implement Unlink and
+// Traverse methods for a given cycle collectable type.
+#define MOZ_CC_UNLINK_IMPL __attribute__((annotate("moz_cc_unlink_impl")))
+#define MOZ_CC_TRAVERSE_IMPL __attribute__((annotate("moz_cc_traverse_impl")))
+
+// This annotation can be placed within a traverse or unlink implementation to
+// tell the analysis to ignore any failures which occur within that
+// implementation.
+#define MOZ_CC_IGNORE_FAILURES_FUNCTION __attribute__((annotate("moz_cc_ignore_failures_function")))
+inline void MOZ_CC_IGNORE_FAILURES_FUNCTION
+CycleCollectionIgnoreStaticAnalysisFailures()
+{}
+
+#define MOZ_CC_IGNORE_FAILURES CycleCollectionIgnoreStaticAnalysisFailures();
+
+
+
+// Definitions of a series of utility macros and functions which are present in
+// real Traverse/Unlink impls. These are intended to make the test cases look
+// more realistic
+template<typename T> T*
+DowncastCCParticipant(void* aPtr) {return nullptr;}
+
+class nsCycleCollectionTraversalCallback;
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS(_class)                      \
+  class cycleCollection {                                           \
+    void Unlink(void *p);                                           \
+    void Traverse(void *p, nsCycleCollectionTraversalCallback &cb); \
+  }
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class)                 \
+  void MOZ_CC_TRAVERSE_IMPL                                             \
+  _class::cycleCollection::Traverse(void *p, nsCycleCollectionTraversalCallback &cb) \
+  {                                                                     \
+    _class *tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+    (void)tmp;                                \
+  }
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+  void MOZ_CC_UNLINK_IMPL                             \
+  _class::cycleCollection::Unlink(void *p)            \
+  {                                                   \
+    _class *tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+    (void)tmp;                              \
+  }
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK(_field) \
+  ImplCycleCollectionUnlink(tmp->_field);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_field) \
+  ImplCycleCollectionTraverse(cb, tmp->_field, #_field, 0);
+
+
+// This is a fake RefPtr class - we want to traverse all RefPtrs inside of a struct
+// definition if they are references to XPCOM or cycle-collectable objects.
+template<typename T>
+class MOZ_CC_TRAVERSABLE_PTR RefPtr {};
+
+// Implementations of ImplCycleCollectionTraverse and ImplCycleCollectionUnlink for RefPtr
+template <typename T>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+                            RefPtr<T>& aField,
+                            const char* aName,
+                            unsigned int aFlags = 0)
+{}
+
+template <typename T>
+inline void
+ImplCycleCollectionUnlink(RefPtr<T>& aField)
+{}
+
+// This is a fake Array class - we want to traverse Arrays when they contain
+// a traversable type - such as a RefPtr.
+template<typename T>
+class MOZ_CC_OWNS_TEMPLATE_ARG Array {};
+
+template<typename T>
+class ArraySub : public Array<T> {};
+
+// Implementations of ImplCycleCollectionTraverse and ImplCycleCollectionUnlink for Array
+template <typename T>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+                            Array<T>& aField,
+                            const char* aName,
+                            unsigned int aFlags = 0)
+{}
+
+template <typename T>
+inline void
+ImplCycleCollectionUnlink(Array<T>& aField)
+{}
+
+class Normal {};
+
+class NativeCC {
+  NS_DECL_CYCLE_COLLECTION_CLASS(NativeCC);
+};
+
+class MOZ_CC nsISupports {};
+class XPCOM : public nsISupports {};
+class MOZ_CC CcXPCOM : public nsISupports {};
+
+// If a RefPtr isn't mentioned, errors should be reported
+class A {
+  NS_DECL_CYCLE_COLLECTION_CLASS(A);
+
+  RefPtr<Normal> rn;
+  Array<Normal> an;
+  ArraySub<Normal> asn;
+  Array<RefPtr<Normal>> arn;
+  ArraySub<RefPtr<Normal>> asrn;
+  Normal n;
+
+  RefPtr<NativeCC> rc; // expected-error {{Traversable member 'rc' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'rc' of class 'A' not mentioned in Unlink implementation}}
+  Array<NativeCC> ac;
+  ArraySub<NativeCC> asc;
+  Array<RefPtr<NativeCC>> arc; // expected-error {{Traversable member 'arc' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'arc' of class 'A' not mentioned in Unlink implementation}}
+  ArraySub<RefPtr<NativeCC>> asrc; // expected-error {{Traversable member 'asrc' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'asrc' of class 'A' not mentioned in Unlink implementation}}
+  NativeCC c;
+
+  RefPtr<CcXPCOM> rx; // expected-error {{Traversable member 'rx' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'rx' of class 'A' not mentioned in Unlink implementation}}
+  Array<CcXPCOM> ax;
+  ArraySub<CcXPCOM> asx;
+  Array<RefPtr<CcXPCOM>> arx; // expected-error {{Traversable member 'arx' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'arx' of class 'A' not mentioned in Unlink implementation}}
+  ArraySub<RefPtr<CcXPCOM>> asrx; // expected-error {{Traversable member 'asrx' of class 'A' not mentioned in Traverse implementation}} expected-error {{Traversable member 'asrx' of class 'A' not mentioned in Unlink implementation}}
+  CcXPCOM x;
+
+  RefPtr<XPCOM> rw;
+  Array<XPCOM> aw;
+  ArraySub<XPCOM> asw;
+  Array<RefPtr<XPCOM>> arw;
+  ArraySub<RefPtr<XPCOM>> asrw;
+  XPCOM w;
+};
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(A)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(A)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+// If the implementation is in a different file, no error should be reported
+class B {
+  NS_DECL_CYCLE_COLLECTION_CLASS(B);
+
+  RefPtr<Normal> rn;
+  Array<Normal> an;
+  ArraySub<Normal> asn;
+  Array<RefPtr<Normal>> arn;
+  ArraySub<RefPtr<Normal>> asrn;
+  Normal n;
+
+  RefPtr<NativeCC> rc;
+  Array<NativeCC> ac;
+  ArraySub<NativeCC> asc;
+  Array<RefPtr<NativeCC>> arc;
+  ArraySub<RefPtr<NativeCC>> asrc;
+  NativeCC c;
+
+  RefPtr<CcXPCOM> rx;
+  Array<CcXPCOM> ax;
+  ArraySub<CcXPCOM> asx;
+  Array<RefPtr<CcXPCOM>> arx;
+  ArraySub<RefPtr<CcXPCOM>> asrx;
+  CcXPCOM x;
+
+  RefPtr<XPCOM> rw;
+  Array<XPCOM> aw;
+  ArraySub<XPCOM> asw;
+  Array<RefPtr<XPCOM>> arw;
+  ArraySub<RefPtr<XPCOM>> asrw;
+  XPCOM w;
+};
+
+// If it is mentioned, no error should be reported
+class C {
+  NS_DECL_CYCLE_COLLECTION_CLASS(C);
+
+  RefPtr<Normal> rn;
+  Array<Normal> an;
+  ArraySub<Normal> asn;
+  Array<RefPtr<Normal>> arn;
+  ArraySub<RefPtr<Normal>> asrn;
+  Normal n;
+
+  RefPtr<NativeCC> rc;
+  Array<NativeCC> ac;
+  ArraySub<NativeCC> asc;
+  Array<RefPtr<NativeCC>> arc;
+  ArraySub<RefPtr<NativeCC>> asrc;
+  NativeCC c;
+
+  RefPtr<CcXPCOM> rx;
+  Array<CcXPCOM> ax;
+  ArraySub<CcXPCOM> asx;
+  Array<RefPtr<CcXPCOM>> arx;
+  ArraySub<RefPtr<CcXPCOM>> asrx;
+  CcXPCOM x;
+
+  RefPtr<XPCOM> rw;
+  Array<XPCOM> aw;
+  ArraySub<XPCOM> asw;
+  Array<RefPtr<XPCOM>> arw;
+  ArraySub<RefPtr<XPCOM>> asrw;
+  XPCOM w;
+};
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(C)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(rc)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(arc)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(asrc)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(rx)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(arx)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(asrx)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(C)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(rc)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(arc)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(asrc)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(rx)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(arx)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(asrx)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+// If a RefPtr isn't mentioned, errors should be reported
+class D {
+  NS_DECL_CYCLE_COLLECTION_CLASS(D);
+
+  RefPtr<Normal> rn;
+  Array<Normal> an;
+  ArraySub<Normal> asn;
+  Array<RefPtr<Normal>> arn;
+  ArraySub<RefPtr<Normal>> asrn;
+  Normal n;
+
+  RefPtr<NativeCC> rc;
+  Array<NativeCC> ac;
+  ArraySub<NativeCC> asc;
+  Array<RefPtr<NativeCC>> arc;
+  ArraySub<RefPtr<NativeCC>> asrc;
+  NativeCC c;
+
+  RefPtr<CcXPCOM> rx;
+  Array<CcXPCOM> ax;
+  ArraySub<CcXPCOM> asx;
+  Array<RefPtr<CcXPCOM>> arx;
+  ArraySub<RefPtr<CcXPCOM>> asrx;
+  CcXPCOM x;
+
+  RefPtr<XPCOM> rw;
+  Array<XPCOM> aw;
+  ArraySub<XPCOM> asw;
+  Array<RefPtr<XPCOM>> arw;
+  ArraySub<RefPtr<XPCOM>> asrw;
+  XPCOM w;
+};
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(D)
+  MOZ_CC_IGNORE_FAILURES
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(D)
+  MOZ_CC_IGNORE_FAILURES
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestMozCCXPCOMBase.cpp
@@ -0,0 +1,27 @@
+#define MOZ_CC __attribute__((annotate("moz_cc")))
+
+class MOZ_CC nsISupports {};
+
+class NonCycleCollected : public nsISupports {};
+
+class NonCycleCollectedBase1 : public nsISupports {};
+
+class NonCycleCollected1 : public NonCycleCollectedBase1 {};
+
+class NonCycleCollectedBase2 : public nsISupports {}; // expected-error {{Class 'NonCycleCollectedBase2' must be annotated with MOZ_CC (or [cyclecollect] in XPIDL)}}
+
+class CycleCollected2 : public NonCycleCollectedBase2 { // expected-note {{Class 'NonCycleCollectedBase2' is an XPCOM base class for the cycle-collected class 'CycleCollected2'}}
+  class cycleCollection {};
+};
+
+class MOZ_CC NonCycleCollectedBase3 : public nsISupports {};
+
+class CycleCollected3 : public NonCycleCollectedBase3 {
+  class cycleCollection {};
+};
+
+class NonXPCOMNonCycleCollectedBase {};
+
+class NonXPCOMCycleCollected : public NonXPCOMNonCycleCollectedBase {
+  class cycleCollection {};
+};
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -2,20 +2,22 @@
 # vim: set filetype=python:
 # 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/.
 
 SOURCES += [
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
+    'TestCycleCollection.cpp',
     'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
     'TestHeapClass.cpp',
     'TestInheritTypeAnnotationsFromTemplateArgs.cpp',
+    'TestMozCCXPCOMBase.cpp',
     'TestMultipleAnnotations.cpp',
     'TestMustOverride.cpp',
     'TestMustUse.cpp',
     'TestNANTestingExpr.cpp',
     'TestNANTestingExprC.c',
     'TestNeedsNoVTableType.cpp',
     'TestNoAddRefReleaseOnReturn.cpp',
     'TestNoArithmeticExprInArgument.cpp',
--- a/mfbt/Attributes.h
+++ b/mfbt/Attributes.h
@@ -514,16 +514,22 @@
 #  define MOZ_NO_ADDREF_RELEASE_ON_RETURN __attribute__((annotate("moz_no_addref_release_on_return")))
 #  define MOZ_MUST_USE __attribute__((annotate("moz_must_use")))
 #  define MOZ_NEEDS_NO_VTABLE_TYPE __attribute__((annotate("moz_needs_no_vtable_type")))
 #  define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable")))
 #  define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type")))
 #  define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS \
     __attribute__((annotate("moz_inherit_type_annotations_from_template_args")))
 #  define MOZ_NON_AUTOABLE __attribute__((annotate("moz_non_autoable")))
+#  define MOZ_CC_TRAVERSABLE_PTR __attribute__((annotate("moz_cc_traversable_ptr")))
+#  define MOZ_CC_TRAVERSE __attribute__((annotate("moz_cc_traverse")))
+#  define MOZ_CC_OWNS_TEMPLATE_ARG __attribute__((annotate("moz_cc_owns_template_arg")))
+#  define MOZ_CC_UNLINK_IMPL __attribute__((annotate("moz_cc_unlink_impl")))
+#  define MOZ_CC_TRAVERSE_IMPL __attribute__((annotate("moz_cc_traverse_impl")))
+
 /*
  * It turns out that clang doesn't like void func() __attribute__ {} without a
  * warning, so use pragmas to disable the warning. This code won't work on GCC
  * anyways, so the warning is safe to ignore.
  */
 #  define MOZ_HEAP_ALLOCATOR \
     _Pragma("clang diagnostic push") \
     _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \
@@ -545,16 +551,20 @@
 #  define MOZ_UNSAFE_REF(reason) /* nothing */
 #  define MOZ_NO_ADDREF_RELEASE_ON_RETURN /* nothing */
 #  define MOZ_MUST_USE /* nothing */
 #  define MOZ_NEEDS_NO_VTABLE_TYPE /* nothing */
 #  define MOZ_NON_MEMMOVABLE /* nothing */
 #  define MOZ_NEEDS_MEMMOVABLE_TYPE /* nothing */
 #  define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS /* nothing */
 #  define MOZ_NON_AUTOABLE /* nothing */
+#  define MOZ_CC_TRAVERSE /* nothing */
+#  define MOZ_CC_OWNS_TEMPLATE_ARG /* nothing */
+#  define MOZ_CC_UNLINK_IMPL /* nothing */
+#  define MOZ_CC_TRAVERSE_IMPL /* nothing */
 #endif /* MOZ_CLANG_PLUGIN */
 
 #define MOZ_RAII MOZ_NON_TEMPORARY_CLASS MOZ_STACK_CLASS
 
 /*
  * MOZ_HAVE_REF_QUALIFIERS is defined for compilers that support C++11's rvalue
  * qualifier, "&&".
  */
--- a/mfbt/EnumeratedArray.h
+++ b/mfbt/EnumeratedArray.h
@@ -35,17 +35,17 @@ namespace mozilla {
  *
  *   headCount[AnimalSpecies::Cow] = 17;
  *   headCount[AnimalSpecies::Sheep] = 30;
  *
  */
 template<typename IndexType,
          IndexType SizeAsEnumValue,
          typename ValueType>
-class EnumeratedArray
+class MOZ_CC_OWNS_TEMPLATE_ARG EnumeratedArray
 {
 public:
   static const size_t kSize = size_t(SizeAsEnumValue);
 
 private:
   Array<ValueType, kSize> mArray;
 
 public:
--- a/mfbt/RefPtr.h
+++ b/mfbt/RefPtr.h
@@ -39,17 +39,17 @@ template<typename T> OutParamRef<T> byRe
  * RefPtr<T> is assigned a T*, the T* can be used through the RefPtr
  * as if it were a T*.
  *
  * A RefPtr can forget its underlying T*, which results in the T*
  * being wrapped in a temporary object until the T* is either
  * re-adopted from or released by the temporary.
  */
 template<typename T>
-class RefPtr
+class MOZ_CC_TRAVERSABLE_PTR RefPtr
 {
   // To allow them to use unref()
   friend class OutParamRef<T>;
 
   struct DontRef {};
 
 public:
   RefPtr() : mPtr(0) {}
--- a/mfbt/nsRefPtr.h
+++ b/mfbt/nsRefPtr.h
@@ -18,17 +18,17 @@
 class nsCOMPtr_helper;
 
 namespace mozilla {
 template<class T> class OwningNonNull;
 template<class T> class RefPtr;
 } // namespace mozilla
 
 template <class T>
-class nsRefPtr
+class MOZ_CC_TRAVERSABLE_PTR nsRefPtr
 {
 private:
   void
   assign_with_AddRef(T* aRawPtr)
   {
     if (aRawPtr) {
       AddRefTraits<T>::AddRef(aRawPtr);
     }
--- a/xpcom/glue/nsCOMArray.h
+++ b/xpcom/glue/nsCOMArray.h
@@ -13,17 +13,17 @@
 #include "nsCycleCollectionNoteChild.h"
 #include "nsTArray.h"
 #include "nsISupports.h"
 
 // See below for the definition of nsCOMArray<T>
 
 // a class that's nsISupports-specific, so that we can contain the
 // work of this class in the XPCOM dll
-class nsCOMArray_base
+class MOZ_CC_TRAVERSE nsCOMArray_base
 {
   friend class nsArrayBase;
 protected:
   nsCOMArray_base() {}
   explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {}
   nsCOMArray_base(const nsCOMArray_base& aOther);
   ~nsCOMArray_base();
 
--- a/xpcom/glue/nsCOMPtr.h
+++ b/xpcom/glue/nsCOMPtr.h
@@ -341,17 +341,17 @@ protected:
 
 // Helper for assert_validity method
 template<class T>
 char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2];
 template<class T>
 char TestForIID(...);
 
 template<class T>
-class nsCOMPtr final
+class MOZ_CC_TRAVERSABLE_PTR nsCOMPtr final
 #ifdef NSCAP_FEATURE_USE_BASE
   : private nsCOMPtr_base
 #endif
 {
 
 #ifdef NSCAP_FEATURE_USE_BASE
   #define NSCAP_CTOR_BASE(x) nsCOMPtr_base(x)
 #else
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -357,17 +357,17 @@ DowncastCCParticipant(void* aPtr)
 // You need to use NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED if you want
 // the base class Unlink version to be called before your own implementation.
 // You can use NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED if you want the
 // base class Unlink to get called after your own implementation.  You should
 // never use them together.
 ///////////////////////////////////////////////////////////////////////////////
 
 #define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class)                          \
-  NS_IMETHODIMP_(void)                                                         \
+  NS_IMETHODIMP_(void) MOZ_CC_UNLINK_IMPL                                      \
   NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(void *p)                       \
   {                                                                            \
     _class *tmp = DowncastCCParticipant<_class >(p);
 
 #define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base_class)   \
   NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class)                                \
     nsISupports *s = static_cast<nsISupports*>(p);                             \
     NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s);
@@ -397,17 +397,17 @@ DowncastCCParticipant(void* aPtr)
 ///////////////////////////////////////////////////////////////////////////////
 // Helpers for implementing nsCycleCollectionParticipant::Traverse
 ///////////////////////////////////////////////////////////////////////////////
 
 #define NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, _refcnt)                     \
     cb.DescribeRefCountedNode(_refcnt, #_class);
 
 #define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class)               \
-  NS_IMETHODIMP                                                                \
+  NS_IMETHODIMP MOZ_CC_TRAVERSE_IMPL                                           \
   NS_CYCLE_COLLECTION_CLASSNAME(_class)::Traverse                              \
                          (void *p, nsCycleCollectionTraversalCallback &cb)     \
   {                                                                            \
     _class *tmp = DowncastCCParticipant<_class >(p);
 
 #define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class)                        \
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class)                     \
   NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get())
--- a/xpcom/glue/nsTArray.h
+++ b/xpcom/glue/nsTArray.h
@@ -336,17 +336,17 @@ struct nsTArray_SafeElementAtHelper<mozi
 };
 
 //
 // This class serves as a base class for nsTArray.  It shouldn't be used
 // directly.  It holds common implementation code that does not depend on the
 // element type of the nsTArray.
 //
 template<class Alloc, class Copy>
-class nsTArray_base
+class MOZ_CC_OWNS_TEMPLATE_ARG nsTArray_base
 {
   // Allow swapping elements with |nsTArray_base|s created using a
   // different allocator.  This is kosher because all allocators use
   // the same free().
   template<class Allocator, class Copier>
   friend class nsTArray_base;
 
 protected:
--- a/xpcom/glue/nsTHashtable.h
+++ b/xpcom/glue/nsTHashtable.h
@@ -68,17 +68,17 @@
  *
  * @see nsInterfaceHashtable
  * @see nsDataHashtable
  * @see nsClassHashtable
  * @author "Benjamin Smedberg <bsmedberg@covad.net>"
  */
 
 template<class EntryType>
-class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable
+class MOZ_NEEDS_NO_VTABLE_TYPE MOZ_CC_OWNS_TEMPLATE_ARG nsTHashtable
 {
   typedef mozilla::fallible_t fallible_t;
 
 public:
   // Separate constructors instead of default aInitLength parameter since
   // otherwise the default no-arg constructor isn't found.
   nsTHashtable()
     : mTable(Ops(), sizeof(EntryType), PLDHashTable::kDefaultInitialLength)