]> granicus.if.org Git - clang/commitdiff
[analyzer] Move out tracking retain count for OSObjects into a separate checker
authorGeorge Karpenkov <ekarpenkov@apple.com>
Fri, 7 Dec 2018 20:21:51 +0000 (20:21 +0000)
committerGeorge Karpenkov <ekarpenkov@apple.com>
Fri, 7 Dec 2018 20:21:51 +0000 (20:21 +0000)
Allow enabling and disabling tracking of ObjC/CF objects
separately from tracking of OS objects.

Differential Revision: https://reviews.llvm.org/D55400

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

include/clang/StaticAnalyzer/Checkers/Checkers.td
include/clang/StaticAnalyzer/Core/RetainSummaryManager.h
lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp
lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h
lib/StaticAnalyzer/Core/RetainSummaryManager.cpp
test/Analysis/osobject-retain-release.cpp
test/Analysis/test-separate-retaincount.cpp [new file with mode: 0644]

index 2933c3e855f26615d43682e04aa68f1687a90a42..9feb5a8766871a292b83a7061a19612764aca682 100644 (file)
@@ -520,6 +520,9 @@ def MacOSKeychainAPIChecker : Checker<"SecKeychainAPI">,
 def ObjCPropertyChecker : Checker<"ObjCProperty">,
   HelpText<"Check for proper uses of Objective-C properties">;
 
+def OSObjectRetainCountChecker : Checker<"OSObjectRetainCount">,
+  HelpText<"Check for leaks and improper reference count management for OSObject">;
+
 } // end "osx"
 
 let ParentPackage = Cocoa in {
index c2e9eaa6220c8d5747243410957c8f9f423c7ef4..de16a1781a0361c1af9ea2682061c5c835b28819 100644 (file)
@@ -469,6 +469,8 @@ public:
   }
 };
 
+class RetainSummaryTemplate;
+
 class RetainSummaryManager {
   typedef llvm::DenseMap<const FunctionDecl*, const RetainSummary *>
           FuncSummariesTy;
@@ -483,7 +485,10 @@ class RetainSummaryManager {
   /// Records whether or not the analyzed code runs in ARC mode.
   const bool ARCEnabled;
 
-  /// Track sublcasses of OSObject
+  /// Track Objective-C and CoreFoundation objects.
+  const bool TrackObjCAndCFObjects;
+
+  /// Track sublcasses of OSObject.
   const bool TrackOSObjects;
 
   /// FuncSummaries - A map from FunctionDecls to summaries.
@@ -626,13 +631,36 @@ class RetainSummaryManager {
   const RetainSummary * generateSummary(const FunctionDecl *FD,
                                         bool &AllowAnnotations);
 
+  /// Return a summary for OSObject, or nullptr if not found.
+  const RetainSummary *getSummaryForOSObject(const FunctionDecl *FD,
+                                             StringRef FName, QualType RetTy);
+
+  /// Return a summary for Objective-C or CF object, or nullptr if not found.
+  const RetainSummary *getSummaryForObjCOrCFObject(
+    const FunctionDecl *FD,
+    StringRef FName,
+    QualType RetTy,
+    const FunctionType *FT,
+    bool &AllowAnnotations);
+
+  /// Apply the annotation of {@code pd} in function {@code FD}
+  /// to the resulting summary stored in out-parameter {@code Template}.
+  /// \return whether an annotation was applied.
+  bool applyFunctionParamAnnotationEffect(const ParmVarDecl *pd,
+                                        unsigned parm_idx,
+                                        const FunctionDecl *FD,
+                                        ArgEffects::Factory &AF,
+                                        RetainSummaryTemplate &Template);
+
 public:
   RetainSummaryManager(ASTContext &ctx,
                        bool usesARC,
-                       bool trackOSObject)
+                       bool trackObjCAndCFObjects,
+                       bool trackOSObjects)
    : Ctx(ctx),
      ARCEnabled(usesARC),
-     TrackOSObjects(trackOSObject),
+     TrackObjCAndCFObjects(trackObjCAndCFObjects),
+     TrackOSObjects(trackOSObjects),
      AF(BPAlloc), ScratchArgs(AF.getEmptyMap()),
      ObjCAllocRetE(usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC)
                                : RetEffect::MakeOwned(RetEffect::ObjC)),
@@ -709,6 +737,7 @@ public:
   void updateSummaryFromAnnotations(const RetainSummary *&Summ,
                                     const FunctionDecl *FD);
 
+
   void updateSummaryForCall(const RetainSummary *&Summ,
                             const CallEvent &Call);
 
@@ -716,9 +745,21 @@ public:
 
   RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; }
 
+  /// \return True if the declaration has an attribute {@code T},
+  /// AND we are tracking that attribute. False otherwise.
+  template <class T>
+  bool hasEnabledAttr(const Decl *D) {
+    return isAttrEnabled<T>() && D->hasAttr<T>();
+  }
+
+  /// Check whether we are tracking properties specified by the attributes.
+  template <class T>
+  bool isAttrEnabled();
+
   friend class RetainSummaryTemplate;
 };
 
+
 // Used to avoid allocating long-term (BPAlloc'd) memory for default retain
 // summaries. If a function or method looks like it has a default summary, but
 // it has annotations, the annotations are added to the stack-based template
index 424cabc6d82c57b42f846aa0723c037c6a6f49e0..02c482e6196f14a8e1d9c2901696432f23947fe9 100644 (file)
@@ -1504,9 +1504,10 @@ void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State,
 
 void ento::registerRetainCountChecker(CheckerManager &Mgr) {
   auto *Chk = Mgr.registerChecker<RetainCountChecker>();
+  Chk->TrackObjCAndCFObjects = true;
+}
 
-  AnalyzerOptions &Options = Mgr.getAnalyzerOptions();
-
-  Chk->ShouldCheckOSObjectRetainCount = Options.getCheckerBooleanOption(
-                                                    "CheckOSObject", true, Chk);
+void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) {
+  auto *Chk = Mgr.registerChecker<RetainCountChecker>();
+  Chk->TrackOSObjects = true;
 }
index f1ffed550a690e6a86bfdbab596b70b6739d4952..0f43e8f5dd458fa42a56d28ff723a8e9cec7c2bb 100644 (file)
@@ -92,7 +92,7 @@ private:
   /// See the RefVal::Kind enum for possible values.
   unsigned RawKind : 5;
 
-  /// The kind of object being tracked (CF or ObjC), if known.
+  /// The kind of object being tracked (CF or ObjC or OSObject), if known.
   ///
   /// See the RetEffect::ObjKind enum for possible values.
   unsigned RawObjectKind : 3;
@@ -268,10 +268,12 @@ class RetainCountChecker
   mutable bool ShouldResetSummaryLog;
 
 public:
-  /// Optional setting to indicate if leak reports should include
-  /// the allocation line.
-  bool IncludeAllocationLine;
-  bool ShouldCheckOSObjectRetainCount;
+
+  /// Track Objective-C and CoreFoundation objects.
+  bool TrackObjCAndCFObjects = false;
+
+  /// Track sublcasses of OSObject.
+  bool TrackOSObjects = false;
 
   RetainCountChecker() : ShouldResetSummaryLog(false) {}
 
@@ -290,7 +292,7 @@ public:
     bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount;
     if (!Summaries) {
       Summaries.reset(new RetainSummaryManager(
-          Ctx, ARCEnabled, ShouldCheckOSObjectRetainCount));
+          Ctx, ARCEnabled, TrackObjCAndCFObjects, TrackOSObjects));
     } else {
       assert(Summaries->isARCEnabled() == ARCEnabled);
     }
index 67efa542d824eb841523ca2844f9f493f274884c..3bbb4c7f9ab1b40a1288a139f3567944e9a8787e 100644 (file)
 using namespace clang;
 using namespace ento;
 
+template <class T>
+constexpr static bool isOneOf() {
+  return false;
+}
+
+/// Helper function to check whether the class is one of the
+/// rest of varargs.
+template <class T, class P, class... ToCompare>
+constexpr static bool isOneOf() {
+  return std::is_same<T, P>::value || isOneOf<T, ToCompare...>();
+}
+
+template <class T> bool RetainSummaryManager::isAttrEnabled() {
+  if (isOneOf<T, CFConsumedAttr, CFReturnsRetainedAttr,
+              CFReturnsNotRetainedAttr, NSConsumedAttr, NSConsumesSelfAttr,
+              NSReturnsAutoreleasedAttr, NSReturnsRetainedAttr,
+              NSReturnsNotRetainedAttr>()) {
+    return TrackObjCAndCFObjects;
+  } else if (isOneOf<T, OSConsumedAttr, OSConsumesThisAttr,
+                     OSReturnsNotRetainedAttr, OSReturnsRetainedAttr>()) {
+    return TrackOSObjects;
+  }
+  llvm_unreachable("Unexpected attribute passed");
+}
+
 ArgEffects RetainSummaryManager::getArgEffects() {
   ArgEffects AE = ScratchArgs;
   ScratchArgs = AF.getEmptyMap();
@@ -116,30 +141,60 @@ static bool isOSObjectRelated(const CXXMethodDecl *MD) {
 }
 
 const RetainSummary *
-RetainSummaryManager::generateSummary(const FunctionDecl *FD,
-                                      bool &AllowAnnotations) {
-  // We generate "stop" summaries for implicitly defined functions.
-  if (FD->isImplicit()) {
-    return getPersistentStopSummary();
+RetainSummaryManager::getSummaryForOSObject(const FunctionDecl *FD,
+                                            StringRef FName, QualType RetTy) {
+  if (RetTy->isPointerType()) {
+    const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl();
+    if (PD && isOSObjectSubclass(PD)) {
+      if (const IdentifierInfo *II = FD->getIdentifier()) {
+        if (isOSObjectDynamicCast(II->getName()))
+          return getDefaultSummary();
+
+        // All objects returned with functions *not* starting with
+        // get, or iterators, are returned at +1.
+        if ((!II->getName().startswith("get") &&
+             !II->getName().startswith("Get")) ||
+            isOSIteratorSubclass(PD)) {
+          return getOSSummaryCreateRule(FD);
+        } else {
+          return getOSSummaryGetRule(FD);
+        }
+      }
+    }
   }
 
-  const IdentifierInfo *II = FD->getIdentifier();
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+    const CXXRecordDecl *Parent = MD->getParent();
+    if (TrackOSObjects && Parent && isOSObjectSubclass(Parent)) {
+      if (FName == "release")
+        return getOSSummaryReleaseRule(FD);
 
-  StringRef FName = II ? II->getName() : "";
+      if (FName == "retain")
+        return getOSSummaryRetainRule(FD);
 
-  // Strip away preceding '_'.  Doing this here will effect all the checks
-  // down below.
-  FName = FName.substr(FName.find_first_not_of('_'));
+      if (FName == "free")
+        return getOSSummaryFreeRule(FD);
+
+      if (MD->getOverloadedOperator() == OO_New)
+        return getOSSummaryCreateRule(MD);
+    }
+  }
+
+  return nullptr;
+}
+
+const RetainSummary *RetainSummaryManager::getSummaryForObjCOrCFObject(
+    const FunctionDecl *FD,
+    StringRef FName,
+    QualType RetTy,
+    const FunctionType *FT,
+    bool &AllowAnnotations) {
 
-  // Inspect the result type. Strip away any typedefs.
-  const auto *FT = FD->getType()->getAs<FunctionType>();
-  QualType RetTy = FT->getReturnType();
   std::string RetTyName = RetTy.getAsString();
 
   // FIXME: This should all be refactored into a chain of "summary lookup"
   //  filters.
   assert(ScratchArgs.isEmpty());
-
   if (FName == "pthread_create" || FName == "pthread_setspecific") {
     // Part of: <rdar://problem/7299394> and <rdar://problem/11282706>.
     // This will be addressed better with IPA.
@@ -230,30 +285,11 @@ RetainSummaryManager::generateSummary(const FunctionDecl *FD,
 
   if (RetTy->isPointerType()) {
 
-    const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl();
-    if (TrackOSObjects && PD && isOSObjectSubclass(PD)) {
-      if (const IdentifierInfo *II = FD->getIdentifier()) {
-
-        if (isOSObjectDynamicCast(II->getName()))
-          return getDefaultSummary();
-
-        // All objects returned with functions *not* starting with
-        // get, or iterators, are returned at +1.
-        if ((!II->getName().startswith("get") &&
-             !II->getName().startswith("Get")) ||
-            isOSIteratorSubclass(PD)) {
-          return getOSSummaryCreateRule(FD);
-        } else {
-          return getOSSummaryGetRule(FD);
-        }
-      }
-    }
-
     // For CoreFoundation ('CF') types.
     if (cocoa::isRefType(RetTy, "CF", FName)) {
       if (isRetain(FD, FName)) {
-        // CFRetain isn't supposed to be annotated. However, this may as well
-        // be a user-made "safe" CFRetain function that is incorrectly
+        // CFRetain isn't supposed to be annotated. However, this may as
+        // well be a user-made "safe" CFRetain function that is incorrectly
         // annotated as cf_returns_retained due to lack of better options.
         // We want to ignore such annotation.
         AllowAnnotations = false;
@@ -294,27 +330,9 @@ RetainSummaryManager::generateSummary(const FunctionDecl *FD,
     }
   }
 
-  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
-    const CXXRecordDecl *Parent = MD->getParent();
-    if (TrackOSObjects && Parent && isOSObjectSubclass(Parent)) {
-      if (FName == "release")
-        return getOSSummaryReleaseRule(FD);
-
-      if (FName == "retain")
-        return getOSSummaryRetainRule(FD);
-
-      if (FName == "free")
-        return getOSSummaryFreeRule(FD);
-
-      if (MD->getOverloadedOperator() == OO_New)
-        return getOSSummaryCreateRule(MD);
-    }
-  }
-
   // Check for release functions, the only kind of functions that we care
   // about that don't return a pointer type.
-  if (FName.size() >= 2 && FName[0] == 'C' &&
-      (FName[1] == 'F' || FName[1] == 'G')) {
+  if (FName.startswith("CG") || FName.startswith("CF")) {
     // Test for 'CGCF'.
     FName = FName.substr(FName.startswith("CGCF") ? 4 : 2);
 
@@ -349,11 +367,41 @@ RetainSummaryManager::generateSummary(const FunctionDecl *FD,
     }
   }
 
-  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+  return nullptr;
+}
+
+const RetainSummary *
+RetainSummaryManager::generateSummary(const FunctionDecl *FD,
+                                      bool &AllowAnnotations) {
+  // We generate "stop" summaries for implicitly defined functions.
+  if (FD->isImplicit())
+    return getPersistentStopSummary();
+
+  const IdentifierInfo *II = FD->getIdentifier();
+
+  StringRef FName = II ? II->getName() : "";
+
+  // Strip away preceding '_'.  Doing this here will effect all the checks
+  // down below.
+  FName = FName.substr(FName.find_first_not_of('_'));
+
+  // Inspect the result type. Strip away any typedefs.
+  const auto *FT = FD->getType()->getAs<FunctionType>();
+  QualType RetTy = FT->getReturnType();
+
+  if (TrackOSObjects)
+    if (const RetainSummary *S = getSummaryForOSObject(FD, FName, RetTy))
+      return S;
+
+  if (TrackObjCAndCFObjects)
+    if (const RetainSummary *S =
+            getSummaryForObjCOrCFObject(FD, FName, RetTy, FT, AllowAnnotations))
+      return S;
+
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD))
     if (!(TrackOSObjects && isOSObjectRelated(MD)))
       return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, StopTracking,
                                   DoNothing);
-  }
 
   return getDefaultSummary();
 }
@@ -658,7 +706,7 @@ RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) {
 Optional<RetEffect>
 RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
                                                   const Decl *D) {
-  if (cocoa::isCocoaObjectRef(RetTy)) {
+  if (TrackObjCAndCFObjects && cocoa::isCocoaObjectRef(RetTy)) {
     if (D->hasAttr<NSReturnsRetainedAttr>())
       return ObjCAllocRetE;
 
@@ -670,17 +718,17 @@ RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
     return None;
   }
 
-  if (D->hasAttr<CFReturnsRetainedAttr>()) {
+  if (hasEnabledAttr<CFReturnsRetainedAttr>(D)) {
     return RetEffect::MakeOwned(RetEffect::CF);
-  } else if (D->hasAttr<OSReturnsRetainedAttr>()) {
+  } else if (hasEnabledAttr<OSReturnsRetainedAttr>(D)) {
     return RetEffect::MakeOwned(RetEffect::OS);
   } else if (hasRCAnnotation(D, "rc_ownership_returns_retained")) {
     return RetEffect::MakeOwned(RetEffect::Generalized);
   }
 
-  if (D->hasAttr<CFReturnsNotRetainedAttr>()) {
+  if (hasEnabledAttr<CFReturnsNotRetainedAttr>(D)) {
     return RetEffect::MakeNotOwned(RetEffect::CF);
-  } else if (D->hasAttr<OSReturnsNotRetainedAttr>()) {
+  } else if (hasEnabledAttr<OSReturnsNotRetainedAttr>(D)) {
     return RetEffect::MakeNotOwned(RetEffect::OS);
   } else if (hasRCAnnotation(D, "rc_ownership_returns_not_retained")) {
     return RetEffect::MakeNotOwned(RetEffect::Generalized);
@@ -694,22 +742,20 @@ RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
   return None;
 }
 
-/// Apply the annotation of {@code pd} in function {@code FD}
-/// to the resulting summary stored in out-parameter {@code Template}.
-/// Return whether an annotation was applied.
-bool applyFunctionParamAnnotationEffect(const ParmVarDecl *pd,
+bool RetainSummaryManager::applyFunctionParamAnnotationEffect(const ParmVarDecl *pd,
                                         unsigned parm_idx,
                                         const FunctionDecl *FD,
                                         ArgEffects::Factory &AF,
                                         RetainSummaryTemplate &Template) {
-  if (pd->hasAttr<NSConsumedAttr>()) {
+  if (hasEnabledAttr<NSConsumedAttr>(pd)) {
     Template->addArg(AF, parm_idx, DecRefMsg);
     return true;
-  } else if (pd->hasAttr<CFConsumedAttr>() || pd->hasAttr<OSConsumedAttr>() ||
+  } else if (hasEnabledAttr<CFConsumedAttr>(pd) ||
+             hasEnabledAttr<OSConsumedAttr>(pd) ||
              hasRCAnnotation(pd, "rc_ownership_consumed")) {
     Template->addArg(AF, parm_idx, DecRef);
     return true;
-  } else if (pd->hasAttr<CFReturnsRetainedAttr>() ||
+  } else if (hasEnabledAttr<CFReturnsRetainedAttr>(pd) ||
              hasRCAnnotation(pd, "rc_ownership_returns_retained")) {
     QualType PointeeTy = pd->getType()->getPointeeType();
     if (!PointeeTy.isNull()) {
@@ -718,7 +764,7 @@ bool applyFunctionParamAnnotationEffect(const ParmVarDecl *pd,
         return true;
       }
     }
-  } else if (pd->hasAttr<CFReturnsNotRetainedAttr>()) {
+  } else if (hasEnabledAttr<CFReturnsNotRetainedAttr>(pd)) {
     QualType PointeeTy = pd->getType()->getPointeeType();
     if (!PointeeTy.isNull()) {
       if (coreFoundation::isCFObjectRef(PointeeTy)) {
@@ -760,7 +806,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
   if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD))
     Template->setRetEffect(*RetE);
 
-  if (FD->hasAttr<OSConsumesThisAttr>())
+  if (hasEnabledAttr<OSConsumesThisAttr>(FD))
     Template->setThisEffect(DecRef);
 }
 
@@ -779,8 +825,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
 
   // Effects on the parameters.
   unsigned parm_idx = 0;
-  for (ObjCMethodDecl::param_const_iterator
-         pi=MD->param_begin(), pe=MD->param_end();
+  for (auto pi=MD->param_begin(), pe=MD->param_end();
        pi != pe; ++pi, ++parm_idx) {
     const ParmVarDecl *pd = *pi;
     if (pd->hasAttr<NSConsumedAttr>()) {
@@ -933,6 +978,10 @@ RetainSummaryManager::getMethodSummary(Selector S, const ObjCInterfaceDecl *ID,
                                        const ObjCMethodDecl *MD, QualType RetTy,
                                        ObjCMethodSummariesTy &CachedSummaries) {
 
+  // Objective-C method summaries are only applicable to ObjC and CF objects.
+  if (!TrackObjCAndCFObjects)
+    return getDefaultSummary();
+
   // Look up a summary in our summary cache.
   const RetainSummary *Summ = CachedSummaries.find(ID, S);
 
@@ -1043,7 +1092,9 @@ void RetainSummaryManager::InitializeMethodSummaries() {
 CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) {
   ASTContext &Ctx = MD->getASTContext();
   LangOptions L = Ctx.getLangOpts();
-  RetainSummaryManager M(Ctx, L.ObjCAutoRefCount, /*TrackOSObjects=*/false);
+  RetainSummaryManager M(Ctx, L.ObjCAutoRefCount,
+                         /*TrackNSAndCFObjects=*/true,
+                         /*TrackOSObjects=*/false);
   const RetainSummary *S = M.getMethodSummary(MD);
   CallEffects CE(S->getRetEffect());
   CE.Receiver = S->getReceiverEffect();
@@ -1057,7 +1108,9 @@ CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) {
 CallEffects CallEffects::getEffect(const FunctionDecl *FD) {
   ASTContext &Ctx = FD->getASTContext();
   LangOptions L = Ctx.getLangOpts();
-  RetainSummaryManager M(Ctx, L.ObjCAutoRefCount, /*TrackOSObjects=*/false);
+  RetainSummaryManager M(Ctx, L.ObjCAutoRefCount,
+                         /*TrackNSAndCFObjects=*/true,
+                         /*TrackOSObjects=*/false);
   const RetainSummary *S = M.getFunctionSummary(FD);
   CallEffects CE(S->getRetEffect());
   unsigned N = FD->param_size();
index cf92e2d6788a4de8483111fc5a378fd4cbcd633e..9c8bd78726a4280d912bc9a83dfe1a3b14355564 100644 (file)
@@ -1,4 +1,4 @@
-// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core,osx.cocoa.RetainCount -analyzer-output=text -verify %s
+// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core,osx -analyzer-output=text -verify %s
 
 struct OSMetaClass;
 
diff --git a/test/Analysis/test-separate-retaincount.cpp b/test/Analysis/test-separate-retaincount.cpp
new file mode 100644 (file)
index 0000000..36bcc48
--- /dev/null
@@ -0,0 +1,37 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx -analyzer-disable-checker osx.cocoa.RetainCount -DNO_CF_OBJECT -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx -analyzer-disable-checker osx.OSObjectRetainCount -DNO_OS_OBJECT -verify %s
+
+typedef const void * CFTypeRef;
+extern CFTypeRef CFRetain(CFTypeRef cf);
+extern void CFRelease(CFTypeRef cf);
+
+#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
+extern CFTypeRef CFCreate() CF_RETURNS_RETAINED;
+
+using size_t = decltype(sizeof(int));
+
+struct OSObject {
+  virtual void retain();
+  virtual void release();
+
+  static void * operator new(size_t size);
+  virtual ~OSObject(){}
+};
+
+void cf_overrelease() {
+  CFTypeRef cf = CFCreate();
+  CFRelease(cf);
+  CFRelease(cf);
+#ifndef NO_CF_OBJECT
+  // expected-warning@-2{{Reference-counted object is used after it is released}}
+#endif
+}
+
+void osobject_overrelease() {
+  OSObject *o = new OSObject;
+  o->release();
+  o->release();
+#ifndef NO_OS_OBJECT
+  // expected-warning@-2{{Reference-counted object is used after it is released}}
+#endif
+}