]> granicus.if.org Git - clang/commitdiff
retain/release checker: Hoist code for bug reports above transfer function logic
authorTed Kremenek <kremenek@apple.com>
Wed, 29 Apr 2009 18:50:19 +0000 (18:50 +0000)
committerTed Kremenek <kremenek@apple.com>
Wed, 29 Apr 2009 18:50:19 +0000 (18:50 +0000)
(those diffs are just code moving) and move the logic for "return of owned
object" leak reporting to EvalReturnStmt.

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

lib/Analysis/CFRefCount.cpp
test/Analysis/retain-release.m

index 149fe2346533c5abd375204d1c8e4e94728f6cc8..77b23327e049fb8f6176ab8bfaba1c8286ef5bad 100644 (file)
@@ -1834,1337 +1834,1351 @@ void CFRefCount::BindingsPrinter::Print(std::ostream& Out, const GRState* state,
   Out << nl;
 }
 
-static inline ArgEffect GetArgE(RetainSummary* Summ, unsigned idx) {
-  return Summ ? Summ->getArg(idx) : MayEscape;
-}
-
-static inline RetEffect GetRetEffect(RetainSummary* Summ) {
-  return Summ ? Summ->getRetEffect() : RetEffect::MakeNoRet();
-}
-
-static inline ArgEffect GetReceiverE(RetainSummary* Summ) {
-  return Summ ? Summ->getReceiverEffect() : DoNothing;
-}
-
-static inline bool IsEndPath(RetainSummary* Summ) {
-  return Summ ? Summ->isEndPath() : false;
-}
-
-
-/// GetReturnType - Used to get the return type of a message expression or
-///  function call with the intention of affixing that type to a tracked symbol.
-///  While the the return type can be queried directly from RetEx, when
-///  invoking class methods we augment to the return type to be that of
-///  a pointer to the class (as opposed it just being id).
-static QualType GetReturnType(Expr* RetE, ASTContext& Ctx) {
-
-  QualType RetTy = RetE->getType();
+//===----------------------------------------------------------------------===//
+// Error reporting.
+//===----------------------------------------------------------------------===//
 
-  // FIXME: We aren't handling id<...>.
-  const PointerType* PT = RetTy->getAsPointerType();
-  if (!PT)
-    return RetTy;
-    
-  // If RetEx is not a message expression just return its type.
-  // If RetEx is a message expression, return its types if it is something
-  /// more specific than id.
-  
-  ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(RetE);
+namespace {
   
-  if (!ME || !Ctx.isObjCIdStructType(PT->getPointeeType()))
-    return RetTy;
+  //===-------------===//
+  // Bug Descriptions. //
+  //===-------------===//  
   
-  ObjCInterfaceDecl* D = ME->getClassInfo().first;  
-
-  // At this point we know the return type of the message expression is id.
-  // If we have an ObjCInterceDecl, we know this is a call to a class method
-  // whose type we can resolve.  In such cases, promote the return type to
-  // Class*.  
-  return !D ? RetTy : Ctx.getPointerType(Ctx.getObjCInterfaceType(D));
-}
-
-
-void CFRefCount::EvalSummary(ExplodedNodeSet<GRState>& Dst,
-                             GRExprEngine& Eng,
-                             GRStmtNodeBuilder<GRState>& Builder,
-                             Expr* Ex,
-                             Expr* Receiver,
-                             RetainSummary* Summ,
-                             ExprIterator arg_beg, ExprIterator arg_end,
-                             ExplodedNode<GRState>* Pred) {
+  class VISIBILITY_HIDDEN CFRefBug : public BugType {
+  protected:
+    CFRefCount& TF;
+    
+    CFRefBug(CFRefCount* tf, const char* name) 
+    : BugType(name, "Memory (Core Foundation/Objective-C)"), TF(*tf) {}    
+  public:
+    
+    CFRefCount& getTF() { return TF; }
+    const CFRefCount& getTF() const { return TF; }
+    
+    // FIXME: Eventually remove.
+    virtual const char* getDescription() const = 0;
+    
+    virtual bool isLeak() const { return false; }
+  };
   
-  // Get the state.
-  GRStateRef state(Builder.GetState(Pred), Eng.getStateManager());
-  ASTContext& Ctx = Eng.getStateManager().getContext();
-
-  // Evaluate the effect of the arguments.
-  RefVal::Kind hasErr = (RefVal::Kind) 0;
-  unsigned idx = 0;
-  Expr* ErrorExpr = NULL;
-  SymbolRef ErrorSym = 0;                                        
+  class VISIBILITY_HIDDEN UseAfterRelease : public CFRefBug {
+  public:
+    UseAfterRelease(CFRefCount* tf)
+    : CFRefBug(tf, "Use-after-release") {}
+    
+    const char* getDescription() const {
+      return "Reference-counted object is used after it is released";
+    }    
+  };
   
-  for (ExprIterator I = arg_beg; I != arg_end; ++I, ++idx) {    
-    SVal V = state.GetSValAsScalarOrLoc(*I);    
-    SymbolRef Sym = V.getAsLocSymbol();
-
-    if (Sym)
-      if (RefBindings::data_type* T = state.get<RefBindings>(Sym)) {
-        state = Update(state, Sym, *T, GetArgE(Summ, idx), hasErr);
-        if (hasErr) {
-          ErrorExpr = *I;
-          ErrorSym = Sym;
-          break;
-        }        
-        continue;
-      }
-
-    if (isa<Loc>(V)) {
-      if (loc::MemRegionVal* MR = dyn_cast<loc::MemRegionVal>(&V)) {
-        if (GetArgE(Summ, idx) == DoNothingByRef)
-          continue;
-        
-        // Invalidate the value of the variable passed by reference.
-        
-        // FIXME: Either this logic should also be replicated in GRSimpleVals
-        //  or should be pulled into a separate "constraint engine."
-        
-        // FIXME: We can have collisions on the conjured symbol if the
-        //  expression *I also creates conjured symbols.  We probably want
-        //  to identify conjured symbols by an expression pair: the enclosing
-        //  expression (the context) and the expression itself.  This should
-        //  disambiguate conjured symbols. 
-        
-        const TypedRegion* R = dyn_cast<TypedRegion>(MR->getRegion());
-
-        if (R) {          
-          // Is the invalidated variable something that we were tracking?
-          SymbolRef Sym = state.GetSValAsScalarOrLoc(R).getAsLocSymbol();
-          
-          // Remove any existing reference-count binding.
-          if (Sym) state = state.remove<RefBindings>(Sym);
-          
-          if (R->isBoundable(Ctx)) {
-            // Set the value of the variable to be a conjured symbol.
-            unsigned Count = Builder.getCurrentBlockCount();
-            QualType T = R->getRValueType(Ctx);
-          
-            if (Loc::IsLocType(T) || (T->isIntegerType() && T->isScalarType())){
-              ValueManager &ValMgr = Eng.getValueManager();
-              SVal V = ValMgr.getConjuredSymbolVal(*I, T, Count);
-              state = state.BindLoc(Loc::MakeVal(R), V);
-            }
-            else if (const RecordType *RT = T->getAsStructureType()) {
-              // Handle structs in a not so awesome way.  Here we just
-              // eagerly bind new symbols to the fields.  In reality we
-              // should have the store manager handle this.  The idea is just
-              // to prototype some basic functionality here.  All of this logic
-              // should one day soon just go away.
-              const RecordDecl *RD = RT->getDecl()->getDefinition(Ctx);
-              
-              // No record definition.  There is nothing we can do.
-              if (!RD)
-                continue;
-              
-              MemRegionManager &MRMgr = state.getManager().getRegionManager();
-              
-              // Iterate through the fields and construct new symbols.
-              for (RecordDecl::field_iterator FI=RD->field_begin(Ctx),
-                   FE=RD->field_end(Ctx); FI!=FE; ++FI) {
-                
-                // For now just handle scalar fields.
-                FieldDecl *FD = *FI;
-                QualType FT = FD->getType();
-                
-                if (Loc::IsLocType(FT) || 
-                    (FT->isIntegerType() && FT->isScalarType())) {                  
-                  const FieldRegion* FR = MRMgr.getFieldRegion(FD, R);
-                  ValueManager &ValMgr = Eng.getValueManager();
-                  SVal V = ValMgr.getConjuredSymbolVal(*I, FT, Count);
-                  state = state.BindLoc(Loc::MakeVal(FR), V);
-                }                
-              }
-            }
-            else {
-              // Just blast away other values.
-              state = state.BindLoc(*MR, UnknownVal());
-            }
-          }
-        }
-        else
-          state = state.BindLoc(*MR, UnknownVal());
-      }
-      else {
-        // Nuke all other arguments passed by reference.
-        state = state.Unbind(cast<Loc>(V));
-      }
+  class VISIBILITY_HIDDEN BadRelease : public CFRefBug {
+  public:
+    BadRelease(CFRefCount* tf) : CFRefBug(tf, "Bad release") {}
+    
+    const char* getDescription() const {
+      return "Incorrect decrement of the reference count of an "
+      "object is not owned at this point by the caller";
     }
-    else if (isa<nonloc::LocAsInteger>(V))
-      state = state.Unbind(cast<nonloc::LocAsInteger>(V).getLoc());
-  } 
+  };
   
-  // Evaluate the effect on the message receiver.  
-  if (!ErrorExpr && Receiver) {
-    SymbolRef Sym = state.GetSValAsScalarOrLoc(Receiver).getAsLocSymbol();
-    if (Sym) {
-      if (const RefVal* T = state.get<RefBindings>(Sym)) {
-        state = Update(state, Sym, *T, GetReceiverE(Summ), hasErr);
-        if (hasErr) {
-          ErrorExpr = Receiver;
-          ErrorSym = Sym;
-        }
-      }
+  class VISIBILITY_HIDDEN DeallocGC : public CFRefBug {
+  public:
+    DeallocGC(CFRefCount *tf) : CFRefBug(tf,
+                                         "-dealloc called while using GC") {}
+    
+    const char *getDescription() const {
+      return "-dealloc called while using GC";
     }
-  }
-  
-  // Process any errors.  
-  if (hasErr) {
-    ProcessNonLeakError(Dst, Builder, Ex, ErrorExpr, Pred, state,
-                        hasErr, ErrorSym);
-    return;
-  }
-  
-  // Consult the summary for the return value.  
-  RetEffect RE = GetRetEffect(Summ);
+  };
   
-  switch (RE.getKind()) {
-    default:
-      assert (false && "Unhandled RetEffect."); break;
-      
-    case RetEffect::NoRet: {
-      
-      // Make up a symbol for the return value (not reference counted).
-      // FIXME: This is basically copy-and-paste from GRSimpleVals.  We 
-      //  should compose behavior, not copy it.
-      
-      // FIXME: We eventually should handle structs and other compound types
-      // that are returned by value.
-      
-      QualType T = Ex->getType();
-      
-      if (Loc::IsLocType(T) || (T->isIntegerType() && T->isScalarType())) {
-        unsigned Count = Builder.getCurrentBlockCount();
-        ValueManager &ValMgr = Eng.getValueManager();
-        SVal X = ValMgr.getConjuredSymbolVal(Ex, T, Count);
-        state = state.BindExpr(Ex, X, false);
-      }      
-      
-      break;
-    }
-      
-    case RetEffect::Alias: {
-      unsigned idx = RE.getIndex();
-      assert (arg_end >= arg_beg);
-      assert (idx < (unsigned) (arg_end - arg_beg));
-      SVal V = state.GetSValAsScalarOrLoc(*(arg_beg+idx));
-      state = state.BindExpr(Ex, V, false);
-      break;
-    }
-      
-    case RetEffect::ReceiverAlias: {
-      assert (Receiver);
-      SVal V = state.GetSValAsScalarOrLoc(Receiver);
-      state = state.BindExpr(Ex, V, false);
-      break;
-    }
-      
-    case RetEffect::OwnedAllocatedSymbol:
-    case RetEffect::OwnedSymbol: {
-      unsigned Count = Builder.getCurrentBlockCount();
-      ValueManager &ValMgr = Eng.getValueManager();      
-      SymbolRef Sym = ValMgr.getConjuredSymbol(Ex, Count);
-      QualType RetT = GetReturnType(Ex, ValMgr.getContext());      
-      state = state.set<RefBindings>(Sym, RefVal::makeOwned(RE.getObjKind(),
-                                                            RetT));
-      state = state.BindExpr(Ex, ValMgr.makeRegionVal(Sym), false);
-
-      // FIXME: Add a flag to the checker where allocations are assumed to
-      // *not fail.
-#if 0
-      if (RE.getKind() == RetEffect::OwnedAllocatedSymbol) {
-        bool isFeasible;
-        state = state.Assume(loc::SymbolVal(Sym), true, isFeasible);
-        assert(isFeasible && "Cannot assume fresh symbol is non-null.");        
-      }
-#endif
-      
-      break;
-    }
+  class VISIBILITY_HIDDEN DeallocNotOwned : public CFRefBug {
+  public:
+    DeallocNotOwned(CFRefCount *tf) : CFRefBug(tf,
+                                               "-dealloc sent to non-exclusively owned object") {}
     
-    case RetEffect::GCNotOwnedSymbol:
-    case RetEffect::NotOwnedSymbol: {
-      unsigned Count = Builder.getCurrentBlockCount();
-      ValueManager &ValMgr = Eng.getValueManager();
-      SymbolRef Sym = ValMgr.getConjuredSymbol(Ex, Count);
-      QualType RetT = GetReturnType(Ex, ValMgr.getContext());      
-      state = state.set<RefBindings>(Sym, RefVal::makeNotOwned(RE.getObjKind(),
-                                                               RetT));
-      state = state.BindExpr(Ex, ValMgr.makeRegionVal(Sym), false);
-      break;
+    const char *getDescription() const {
+      return "-dealloc sent to object that may be referenced elsewhere";
     }
-  }
+  };  
   
-  // Generate a sink node if we are at the end of a path.
-  GRExprEngine::NodeTy *NewNode =
-    IsEndPath(Summ) ? Builder.MakeSinkNode(Dst, Ex, Pred, state)
-                    : Builder.MakeNode(Dst, Ex, Pred, state);
+  class VISIBILITY_HIDDEN Leak : public CFRefBug {
+    const bool isReturn;
+  protected:
+    Leak(CFRefCount* tf, const char* name, bool isRet)
+    : CFRefBug(tf, name), isReturn(isRet) {}
+  public:
+    
+    const char* getDescription() const { return ""; }
+    
+    bool isLeak() const { return true; }
+  };
   
-  // Annotate the edge with summary we used.
-  // FIXME: This assumes that we always use the same summary when generating
-  //  this node.
-  if (NewNode) SummaryLog[NewNode] = Summ;
-}
-
-
-void CFRefCount::EvalCall(ExplodedNodeSet<GRState>& Dst,
-                          GRExprEngine& Eng,
-                          GRStmtNodeBuilder<GRState>& Builder,
-                          CallExpr* CE, SVal L,
-                          ExplodedNode<GRState>* Pred) {
-  const FunctionDecl* FD = L.getAsFunctionDecl();
-  RetainSummary* Summ = !FD ? 0 
-                        : Summaries.getSummary(const_cast<FunctionDecl*>(FD));
+  class VISIBILITY_HIDDEN LeakAtReturn : public Leak {
+  public:
+    LeakAtReturn(CFRefCount* tf, const char* name)
+    : Leak(tf, name, true) {}
+  };
   
-  EvalSummary(Dst, Eng, Builder, CE, 0, Summ,
-              CE->arg_begin(), CE->arg_end(), Pred);
-}
-
-void CFRefCount::EvalObjCMessageExpr(ExplodedNodeSet<GRState>& Dst,
-                                     GRExprEngine& Eng,
-                                     GRStmtNodeBuilder<GRState>& Builder,
-                                     ObjCMessageExpr* ME,
-                                     ExplodedNode<GRState>* Pred) {  
-  RetainSummary* Summ;
+  class VISIBILITY_HIDDEN LeakWithinFunction : public Leak {
+  public:
+    LeakWithinFunction(CFRefCount* tf, const char* name)
+    : Leak(tf, name, false) {}
+  };  
   
-  if (Expr* Receiver = ME->getReceiver()) {
-    // We need the type-information of the tracked receiver object
-    // Retrieve it from the state.
-    ObjCInterfaceDecl* ID = 0;
-
-    // FIXME: Wouldn't it be great if this code could be reduced?  It's just
-    // a chain of lookups.
-    // FIXME: Is this really working as expected?  There are cases where
-    //  we just use the 'ID' from the message expression.
-    const GRState* St = Builder.GetState(Pred);
-    SVal V = Eng.getStateManager().GetSValAsScalarOrLoc(St, Receiver);
-
-    SymbolRef Sym = V.getAsLocSymbol();
-    if (Sym) {
-      if (const RefVal* T  = St->get<RefBindings>(Sym)) {
-        QualType Ty = T->getType();
-        
-        if (const PointerType* PT = Ty->getAsPointerType()) {
-          QualType PointeeTy = PT->getPointeeType();
-          
-          if (ObjCInterfaceType* IT = dyn_cast<ObjCInterfaceType>(PointeeTy))
-            ID = IT->getDecl();
-        }
-      }
-    }
+  //===---------===//
+  // Bug Reports.  //
+  //===---------===//
+  
+  class VISIBILITY_HIDDEN CFRefReport : public RangedBugReport {
+  protected:
+    SymbolRef Sym;
+    const CFRefCount &TF;
+  public:
+    CFRefReport(CFRefBug& D, const CFRefCount &tf,
+                ExplodedNode<GRState> *n, SymbolRef sym)
+    : RangedBugReport(D, D.getDescription(), n), Sym(sym), TF(tf) {}
     
-    // FIXME: The receiver could be a reference to a class, meaning that
-    //  we should use the class method.
-    Summ = Summaries.getInstanceMethodSummary(ME, ID);
-
-    // Special-case: are we sending a mesage to "self"?
-    //  This is a hack.  When we have full-IP this should be removed.
-    if (!Summ) {
-      ObjCMethodDecl* MD = 
-        dyn_cast<ObjCMethodDecl>(&Eng.getGraph().getCodeDecl());
-      
-      if (MD) {
-        if (Expr* Receiver = ME->getReceiver()) {
-          SVal X = Eng.getStateManager().GetSValAsScalarOrLoc(St, Receiver);
-          if (loc::MemRegionVal* L = dyn_cast<loc::MemRegionVal>(&X))
-            if (L->getRegion() == Eng.getStateManager().getSelfRegion(St)) {
-              // Create a summmary where all of the arguments "StopTracking".
-              Summ = Summaries.getPersistentSummary(RetEffect::MakeNoRet(),
-                                                    DoNothing,
-                                                    StopTracking);
-            }
-        }
-      }
+    virtual ~CFRefReport() {}
+    
+    CFRefBug& getBugType() {
+      return (CFRefBug&) RangedBugReport::getBugType();
     }
-  }
-  else
-    Summ = Summaries.getClassMethodSummary(ME);
-
-
-  EvalSummary(Dst, Eng, Builder, ME, ME->getReceiver(), Summ,
-              ME->arg_begin(), ME->arg_end(), Pred);
-}
-
-namespace {
-class VISIBILITY_HIDDEN StopTrackingCallback : public SymbolVisitor {
-  GRStateRef state;
-public:
-  StopTrackingCallback(GRStateRef st) : state(st) {}
-  GRStateRef getState() { return state; }
-
-  bool VisitSymbol(SymbolRef sym) {
-    state = state.remove<RefBindings>(sym);
-    return true;
-  }
+    const CFRefBug& getBugType() const {
+      return (const CFRefBug&) RangedBugReport::getBugType();
+    }
+    
+    virtual void getRanges(BugReporter& BR, const SourceRange*& beg,           
+                           const SourceRange*& end) {
+      
+      if (!getBugType().isLeak())
+        RangedBugReport::getRanges(BR, beg, end);
+      else
+        beg = end = 0;
+    }
+    
+    SymbolRef getSymbol() const { return Sym; }
+    
+    PathDiagnosticPiece* getEndPath(BugReporter& BR,
+                                    const ExplodedNode<GRState>* N);
+    
+    std::pair<const char**,const char**> getExtraDescriptiveText();
+    
+    PathDiagnosticPiece* VisitNode(const ExplodedNode<GRState>* N,
+                                   const ExplodedNode<GRState>* PrevN,
+                                   const ExplodedGraph<GRState>& G,
+                                   BugReporter& BR,
+                                   NodeResolver& NR);
+  };
   
-  const GRState* getState() const { return state.getState(); }
-};
+  class VISIBILITY_HIDDEN CFRefLeakReport : public CFRefReport {
+    SourceLocation AllocSite;
+    const MemRegion* AllocBinding;
+  public:
+    CFRefLeakReport(CFRefBug& D, const CFRefCount &tf,
+                    ExplodedNode<GRState> *n, SymbolRef sym,
+                    GRExprEngine& Eng);
+    
+    PathDiagnosticPiece* getEndPath(BugReporter& BR,
+                                    const ExplodedNode<GRState>* N);
+    
+    SourceLocation getLocation() const { return AllocSite; }
+  };  
 } // end anonymous namespace
-  
 
-void CFRefCount::EvalBind(GRStmtNodeBuilderRef& B, SVal location, SVal val) {  
-  // Are we storing to something that causes the value to "escape"?  
-  bool escapes = false;
+void CFRefCount::RegisterChecks(BugReporter& BR) {
+  useAfterRelease = new UseAfterRelease(this);
+  BR.Register(useAfterRelease);
   
-  // A value escapes in three possible cases (this may change):
-  //
-  // (1) we are binding to something that is not a memory region.
-  // (2) we are binding to a memregion that does not have stack storage
-  // (3) we are binding to a memregion with stack storage that the store
-  //     does not understand.  
-  GRStateRef state = B.getState();
-
-  if (!isa<loc::MemRegionVal>(location))
-    escapes = true;
+  releaseNotOwned = new BadRelease(this);
+  BR.Register(releaseNotOwned);
+  
+  deallocGC = new DeallocGC(this);
+  BR.Register(deallocGC);
+  
+  deallocNotOwned = new DeallocNotOwned(this);
+  BR.Register(deallocNotOwned);
+  
+  // First register "return" leaks.
+  const char* name = 0;
+  
+  if (isGCEnabled())
+    name = "Leak of returned object when using garbage collection";
+  else if (getLangOptions().getGCMode() == LangOptions::HybridGC)
+    name = "Leak of returned object when not using garbage collection (GC) in "
+    "dual GC/non-GC code";
   else {
-    const MemRegion* R = cast<loc::MemRegionVal>(location).getRegion();
-    escapes = !B.getStateManager().hasStackStorage(R);
-    
-    if (!escapes) {
-      // To test (3), generate a new state with the binding removed.  If it is
-      // the same state, then it escapes (since the store cannot represent
-      // the binding).
-      escapes = (state == (state.BindLoc(cast<Loc>(location), UnknownVal())));
-    }
+    assert(getLangOptions().getGCMode() == LangOptions::NonGC);
+    name = "Leak of returned object";
   }
-
-  // If our store can represent the binding and we aren't storing to something
-  // that doesn't have local storage then just return and have the simulation
-  // state continue as is.
-  if (!escapes)
-      return;
-
-  // Otherwise, find all symbols referenced by 'val' that we are tracking
-  // and stop tracking them.
-  B.MakeNode(state.scanReachableSymbols<StopTrackingCallback>(val).getState());
-}
-
-std::pair<GRStateRef,bool>
-CFRefCount::HandleSymbolDeath(GRStateManager& VMgr,
-                              const GRState* St, const Decl* CD,
-                              SymbolRef sid,
-                              RefVal V, bool& hasLeak) {
-
-  GRStateRef state(St, VMgr);
-  assert ((!V.isReturnedOwned() || CD) &&
-          "CodeDecl must be available for reporting ReturnOwned errors.");
-
-  if (V.isReturnedOwned() && V.getCount() == 0)
-    if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(CD)) {
-      std::string s = MD->getSelector().getAsString();
-      if (!followsReturnRule(s.c_str())) {
-        hasLeak = true;
-        state = state.set<RefBindings>(sid, V ^ RefVal::ErrorLeakReturned);
-        return std::make_pair(state, true);
-      }
-    }
   
-  // All other cases.
+  leakAtReturn = new LeakAtReturn(this, name);
+  BR.Register(leakAtReturn);
   
-  hasLeak = V.isOwned() || 
-            ((V.isNotOwned() || V.isReturnedOwned()) && V.getCount() > 0);
-
-  if (!hasLeak)
-    return std::make_pair(state.remove<RefBindings>(sid), false);
+  // Second, register leaks within a function/method.
+  if (isGCEnabled())
+    name = "Leak of object when using garbage collection";  
+  else if (getLangOptions().getGCMode() == LangOptions::HybridGC)
+    name = "Leak of object when not using garbage collection (GC) in "
+    "dual GC/non-GC code";
+  else {
+    assert(getLangOptions().getGCMode() == LangOptions::NonGC);
+    name = "Leak";
+  }
   
-  return std::make_pair(state.set<RefBindings>(sid, V ^ RefVal::ErrorLeak),
-                        false);
+  leakWithinFunction = new LeakWithinFunction(this, name);
+  BR.Register(leakWithinFunction);
+  
+  // Save the reference to the BugReporter.
+  this->BR = &BR;
 }
 
+static const char* Msgs[] = {
+  // GC only
+  "Code is compiled to only use garbage collection",    
+  // No GC.
+  "Code is compiled to use reference counts",
+  // Hybrid, with GC.
+  "Code is compiled to use either garbage collection (GC) or reference counts"
+  " (non-GC).  The bug occurs with GC enabled",  
+  // Hybrid, without GC
+  "Code is compiled to use either garbage collection (GC) or reference counts"
+  " (non-GC).  The bug occurs in non-GC mode"
+};
 
+std::pair<const char**,const char**> CFRefReport::getExtraDescriptiveText() {
+  CFRefCount& TF = static_cast<CFRefBug&>(getBugType()).getTF();
+  
+  switch (TF.getLangOptions().getGCMode()) {
+    default:
+      assert(false);
+      
+    case LangOptions::GCOnly:
+      assert (TF.isGCEnabled());
+      return std::make_pair(&Msgs[0], &Msgs[0]+1);      
+      
+    case LangOptions::NonGC:
+      assert (!TF.isGCEnabled());
+      return std::make_pair(&Msgs[1], &Msgs[1]+1);
+      
+    case LangOptions::HybridGC:
+      if (TF.isGCEnabled())
+        return std::make_pair(&Msgs[2], &Msgs[2]+1);
+      else
+        return std::make_pair(&Msgs[3], &Msgs[3]+1);
+  }
+}
 
-// Dead symbols.
-
-
-
- // Return statements.
-
-void CFRefCount::EvalReturn(ExplodedNodeSet<GRState>& Dst,
-                            GRExprEngine& Eng,
-                            GRStmtNodeBuilder<GRState>& Builder,
-                            ReturnStmt* S,
-                            ExplodedNode<GRState>* Pred) {
+static inline bool contains(const llvm::SmallVectorImpl<ArgEffect>& V,
+                            ArgEffect X) {
+  for (llvm::SmallVectorImpl<ArgEffect>::const_iterator I=V.begin(), E=V.end();
+       I!=E; ++I)
+    if (*I == X) return true;
   
-  Expr* RetE = S->getRetValue();
-  if (!RetE)
-    return;
+  return false;
+}
+
+PathDiagnosticPiece* CFRefReport::VisitNode(const ExplodedNode<GRState>* N,
+                                            const ExplodedNode<GRState>* PrevN,
+                                            const ExplodedGraph<GRState>& G,
+                                            BugReporter& BR,
+                                            NodeResolver& NR) {
   
-  GRStateRef state(Builder.GetState(Pred), Eng.getStateManager());
-  SymbolRef Sym = state.GetSValAsScalarOrLoc(RetE).getAsLocSymbol();
+  // Check if the type state has changed.  
+  GRStateManager &StMgr = cast<GRBugReporter>(BR).getStateManager();
+  GRStateRef PrevSt(PrevN->getState(), StMgr);
+  GRStateRef CurrSt(N->getState(), StMgr);
   
-  if (!Sym)
-    return;
-
-  // Get the reference count binding (if any).
-  const RefVal* T = state.get<RefBindings>(Sym);
+  const RefVal* CurrT = CurrSt.get<RefBindings>(Sym);  
+  if (!CurrT) return NULL;
   
-  if (!T)
-    return;
+  const RefVal& CurrV = *CurrT;
+  const RefVal* PrevT = PrevSt.get<RefBindings>(Sym);
   
-  // Change the reference count.  
-  RefVal X = *T;  
+  // Create a string buffer to constain all the useful things we want
+  // to tell the user.
+  std::string sbuf;
+  llvm::raw_string_ostream os(sbuf);
   
-  switch (X.getKind()) {      
-    case RefVal::Owned: { 
-      unsigned cnt = X.getCount();
-      assert (cnt > 0);
-      X = RefVal::makeReturnedOwned(cnt - 1);
-      break;
+  // This is the allocation site since the previous node had no bindings
+  // for this symbol.
+  if (!PrevT) {
+    Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
+    
+    if (CallExpr *CE = dyn_cast<CallExpr>(S)) {
+      // Get the name of the callee (if it is available).
+      SVal X = CurrSt.GetSValAsScalarOrLoc(CE->getCallee());
+      if (const FunctionDecl* FD = X.getAsFunctionDecl())
+        os << "Call to function '" << FD->getNameAsString() <<'\'';
+      else
+        os << "function call";      
+    }          
+    else {
+      assert (isa<ObjCMessageExpr>(S));
+      os << "Method";
+    }
+    
+    if (CurrV.getObjKind() == RetEffect::CF) {
+      os << " returns a Core Foundation object with a ";
+    }
+    else {
+      assert (CurrV.getObjKind() == RetEffect::ObjC);
+      os << " returns an Objective-C object with a ";
     }
+    
+    if (CurrV.isOwned()) {
+      os << "+1 retain count (owning reference).";
       
-    case RefVal::NotOwned: {
-      unsigned cnt = X.getCount();
-      X = cnt ? RefVal::makeReturnedOwned(cnt - 1)
-              : RefVal::makeReturnedNotOwned();
-      break;
+      if (static_cast<CFRefBug&>(getBugType()).getTF().isGCEnabled()) {
+        assert(CurrV.getObjKind() == RetEffect::CF);
+        os << "  "
+        "Core Foundation objects are not automatically garbage collected.";
+      }
     }
-      
-    default: 
-      return;
-  }
-  
-  // Update the binding.
-  state = state.set<RefBindings>(Sym, X);
-  Builder.MakeNode(Dst, S, Pred, state);
-}
-
-// Assumptions.
-
-const GRState* CFRefCount::EvalAssume(GRStateManager& VMgr,
-                                         const GRState* St,
-                                         SVal Cond, bool Assumption,
-                                         bool& isFeasible) {
-
-  // FIXME: We may add to the interface of EvalAssume the list of symbols
-  //  whose assumptions have changed.  For now we just iterate through the
-  //  bindings and check if any of the tracked symbols are NULL.  This isn't
-  //  too bad since the number of symbols we will track in practice are 
-  //  probably small and EvalAssume is only called at branches and a few
-  //  other places.
-  RefBindings B = St->get<RefBindings>();
-  
-  if (B.isEmpty())
-    return St;
-  
-  bool changed = false;
-  
-  GRStateRef state(St, VMgr);
-  RefBindings::Factory& RefBFactory = state.get_context<RefBindings>();
-
-  for (RefBindings::iterator I=B.begin(), E=B.end(); I!=E; ++I) {    
-    // Check if the symbol is null (or equal to any constant).
-    // If this is the case, stop tracking the symbol.
-    if (VMgr.getSymVal(St, I.getKey())) {
-      changed = true;
-      B = RefBFactory.Remove(B, I.getKey());
+    else {
+      assert (CurrV.isNotOwned());
+      os << "+0 retain count (non-owning reference).";
     }
+    
+    PathDiagnosticLocation Pos(S, BR.getContext().getSourceManager());
+    return new PathDiagnosticEventPiece(Pos, os.str());
   }
   
-  if (changed)
-    state = state.set<RefBindings>(B);
+  // Gather up the effects that were performed on the object at this
+  // program point
+  llvm::SmallVector<ArgEffect, 2> AEffects;
   
-  return state;
-}
-
-GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym,
-                              RefVal V, ArgEffect E,
-                              RefVal::Kind& hasErr) {
-
-  // In GC mode [... release] and [... retain] do nothing.
-  switch (E) {
-    default: break;
-    case IncRefMsg: E = isGCEnabled() ? DoNothing : IncRef; break;
-    case DecRefMsg: E = isGCEnabled() ? DoNothing : DecRef; break;
-    case MakeCollectable: E = isGCEnabled() ? DecRef : DoNothing; break;
-    case NewAutoreleasePool: E = isGCEnabled() ? DoNothing : 
-                                                 NewAutoreleasePool; break;
+  if (const RetainSummary *Summ = TF.getSummaryOfNode(NR.getOriginalNode(N))) {
+    // We only have summaries attached to nodes after evaluating CallExpr and
+    // ObjCMessageExprs.
+    Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
+    
+    if (CallExpr *CE = dyn_cast<CallExpr>(S)) {
+      // Iterate through the parameter expressions and see if the symbol
+      // was ever passed as an argument.
+      unsigned i = 0;
+      
+      for (CallExpr::arg_iterator AI=CE->arg_begin(), AE=CE->arg_end();
+           AI!=AE; ++AI, ++i) {
+        
+        // Retrieve the value of the argument.  Is it the symbol
+        // we are interested in?
+        if (CurrSt.GetSValAsScalarOrLoc(*AI).getAsLocSymbol() != Sym)
+          continue;
+        
+        // We have an argument.  Get the effect!
+        AEffects.push_back(Summ->getArg(i));
+      }
+    }
+    else if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {      
+      if (Expr *receiver = ME->getReceiver())
+        if (CurrSt.GetSValAsScalarOrLoc(receiver).getAsLocSymbol() == Sym) {
+          // The symbol we are tracking is the receiver.
+          AEffects.push_back(Summ->getReceiverEffect());
+        }
+    }
   }
   
-  // Handle all use-after-releases.
-  if (!isGCEnabled() && V.getKind() == RefVal::Released) {
-    V = V ^ RefVal::ErrorUseAfterRelease;
-    hasErr = V.getKind();
-    return state.set<RefBindings>(sym, V);
-  }      
-  
-  switch (E) {
-    default:
-      assert (false && "Unhandled CFRef transition.");
-      
-    case Dealloc:
-      // Any use of -dealloc in GC is *bad*.
-      if (isGCEnabled()) {
-        V = V ^ RefVal::ErrorDeallocGC;
-        hasErr = V.getKind();
+  do {
+    // Get the previous type state.
+    RefVal PrevV = *PrevT;
+    
+    // Specially handle -dealloc.
+    if (!TF.isGCEnabled() && contains(AEffects, Dealloc)) {
+      // Determine if the object's reference count was pushed to zero.
+      assert(!(PrevV == CurrV) && "The typestate *must* have changed.");
+      // We may not have transitioned to 'release' if we hit an error.
+      // This case is handled elsewhere.
+      if (CurrV.getKind() == RefVal::Released) {
+        assert(CurrV.getCount() == 0);
+        os << "Object released by directly sending the '-dealloc' message";
         break;
       }
+    }
+    
+    // Specially handle CFMakeCollectable and friends.
+    if (contains(AEffects, MakeCollectable)) {
+      // Get the name of the function.
+      Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
+      SVal X = CurrSt.GetSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee());
+      const FunctionDecl* FD = X.getAsFunctionDecl();
+      const std::string& FName = FD->getNameAsString();
       
-      switch (V.getKind()) {
-        default:
-          assert(false && "Invalid case.");
-        case RefVal::Owned:
-          // The object immediately transitions to the released state.
-          V = V ^ RefVal::Released;
-          V.clearCounts();
-          return state.set<RefBindings>(sym, V);
-        case RefVal::NotOwned:
-          V = V ^ RefVal::ErrorDeallocNotOwned;
-          hasErr = V.getKind();
-          break;
-      }      
-      break;
-
-    case NewAutoreleasePool:
-      assert(!isGCEnabled());
-      return state.add<AutoreleaseStack>(sym);
-      
-    case MayEscape:
-      if (V.getKind() == RefVal::Owned) {
-        V = V ^ RefVal::NotOwned;
-        break;
+      if (TF.isGCEnabled()) {
+        // Determine if the object's reference count was pushed to zero.
+        assert(!(PrevV == CurrV) && "The typestate *must* have changed.");
+        
+        os << "In GC mode a call to '" << FName
+        <<  "' decrements an object's retain count and registers the "
+        "object with the garbage collector. ";
+        
+        if (CurrV.getKind() == RefVal::Released) {
+          assert(CurrV.getCount() == 0);
+          os << "Since it now has a 0 retain count the object can be "
+          "automatically collected by the garbage collector.";
+        }
+        else
+          os << "An object must have a 0 retain count to be garbage collected. "
+          "After this call its retain count is +" << CurrV.getCount()
+          << '.';
       }
-
-      // Fall-through.
-      
-    case DoNothingByRef:
-    case DoNothing:
-      return state;
-
-    case Autorelease:
-      if (isGCEnabled())
-        return state;
-      
-      // Update the autorelease counts.
-      state = SendAutorelease(state, ARCountFactory, sym);
-
-      // Fall-through.
+      else 
+        os << "When GC is not enabled a call to '" << FName
+        << "' has no effect on its argument.";
       
-    case StopTracking:
-      return state.remove<RefBindings>(sym);
-
-    case IncRef:      
-      switch (V.getKind()) {
-        default:
-          assert(false);
-
+      // Nothing more to say.
+      break;
+    }
+    
+    // Determine if the typestate has changed.  
+    if (!(PrevV == CurrV))
+      switch (CurrV.getKind()) {
         case RefVal::Owned:
         case RefVal::NotOwned:
-          V = V + 1;
-          break;          
+          
+          if (PrevV.getCount() == CurrV.getCount())
+            return 0;
+          
+          if (PrevV.getCount() > CurrV.getCount())
+            os << "Reference count decremented.";
+          else
+            os << "Reference count incremented.";
+          
+          if (unsigned Count = CurrV.getCount())
+            os << " The object now has a +" << Count << " retain count.";
+          
+          if (PrevV.getKind() == RefVal::Released) {
+            assert(TF.isGCEnabled() && CurrV.getCount() > 0);
+            os << " The object is not eligible for garbage collection until the "
+            "retain count reaches 0 again.";
+          }
+          
+          break;
+          
         case RefVal::Released:
-          // Non-GC cases are handled above.
-          assert(isGCEnabled());
-          V = (V ^ RefVal::Owned) + 1;
+          os << "Object released.";
           break;
-      }      
-      break;
+          
+        case RefVal::ReturnedOwned:
+          os << "Object returned to caller as an owning reference (single retain "
+          "count transferred to caller).";
+          break;
+          
+        case RefVal::ReturnedNotOwned:
+          os << "Object returned to caller with a +0 (non-owning) retain count.";
+          break;
+          
+        default:
+          return NULL;
+      }
+    
+    // Emit any remaining diagnostics for the argument effects (if any).
+    for (llvm::SmallVectorImpl<ArgEffect>::iterator I=AEffects.begin(),
+         E=AEffects.end(); I != E; ++I) {
+      
+      // A bunch of things have alternate behavior under GC.
+      if (TF.isGCEnabled())
+        switch (*I) {
+          default: break;
+          case Autorelease:
+            os << "In GC mode an 'autorelease' has no effect.";
+            continue;
+          case IncRefMsg:
+            os << "In GC mode the 'retain' message has no effect.";
+            continue;
+          case DecRefMsg:
+            os << "In GC mode the 'release' message has no effect.";
+            continue;
+        }
+    }
+  } while(0);
+  
+  if (os.str().empty())
+    return 0; // We have nothing to say!
+  
+  Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();    
+  PathDiagnosticLocation Pos(S, BR.getContext().getSourceManager());
+  PathDiagnosticPiece* P = new PathDiagnosticEventPiece(Pos, os.str());
+  
+  // Add the range by scanning the children of the statement for any bindings
+  // to Sym.
+  for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
+    if (Expr* Exp = dyn_cast_or_null<Expr>(*I))
+      if (CurrSt.GetSValAsScalarOrLoc(Exp).getAsLocSymbol() == Sym) {
+        P->addRange(Exp->getSourceRange());
+        break;
+      }
+  
+  return P;
+}
+
+namespace {
+  class VISIBILITY_HIDDEN FindUniqueBinding :
+  public StoreManager::BindingsHandler {
+    SymbolRef Sym;
+    const MemRegion* Binding;
+    bool First;
+    
+  public:
+    FindUniqueBinding(SymbolRef sym) : Sym(sym), Binding(0), First(true) {}
+    
+    bool HandleBinding(StoreManager& SMgr, Store store, const MemRegion* R,
+                       SVal val) {
       
-    case SelfOwn:
-      V = V ^ RefVal::NotOwned;
-      // Fall-through.
-    case DecRef:
-      switch (V.getKind()) {
-        default:
-          // case 'RefVal::Released' handled above.
-          assert (false);
+      SymbolRef SymV = val.getAsSymbol();    
+      if (!SymV || SymV != Sym)
+        return true;
+      
+      if (Binding) {
+        First = false;
+        return false;
+      }
+      else
+        Binding = R;
+      
+      return true;    
+    }
+    
+    operator bool() { return First && Binding; }
+    const MemRegion* getRegion() { return Binding; }
+  };  
+}
 
-        case RefVal::Owned:
-          assert(V.getCount() > 0);
-          if (V.getCount() == 1) V = V ^ RefVal::Released;
-          V = V - 1;
-          break;
-          
-        case RefVal::NotOwned:
-          if (V.getCount() > 0)
-            V = V - 1;
-          else {
-            V = V ^ RefVal::ErrorReleaseNotOwned;
-            hasErr = V.getKind();
-          }          
-          break;
-          
-        case RefVal::Released:
-          // Non-GC cases are handled above.
-          assert(isGCEnabled());
-          V = V ^ RefVal::ErrorUseAfterRelease;
-          hasErr = V.getKind();
-          break;  
-      }      
+static std::pair<const ExplodedNode<GRState>*,const MemRegion*>
+GetAllocationSite(GRStateManager& StateMgr, const ExplodedNode<GRState>* N,
+                  SymbolRef Sym) {
+  
+  // Find both first node that referred to the tracked symbol and the
+  // memory location that value was store to.
+  const ExplodedNode<GRState>* Last = N;
+  const MemRegion* FirstBinding = 0;  
+  
+  while (N) {
+    const GRState* St = N->getState();
+    RefBindings B = St->get<RefBindings>();
+    
+    if (!B.lookup(Sym))
       break;
+    
+    FindUniqueBinding FB(Sym);
+    StateMgr.iterBindings(St, FB);      
+    if (FB) FirstBinding = FB.getRegion();      
+    
+    Last = N;
+    N = N->pred_empty() ? NULL : *(N->pred_begin());    
   }
-  return state.set<RefBindings>(sym, V);
+  
+  return std::make_pair(Last, FirstBinding);
 }
 
-//===----------------------------------------------------------------------===//
-// Error reporting.
-//===----------------------------------------------------------------------===//
+PathDiagnosticPiece*
+CFRefReport::getEndPath(BugReporter& br, const ExplodedNode<GRState>* EndN) {
+  // Tell the BugReporter to report cases when the tracked symbol is
+  // assigned to different variables, etc.
+  GRBugReporter& BR = cast<GRBugReporter>(br);
+  cast<GRBugReporter>(BR).addNotableSymbol(Sym);
+  return RangedBugReport::getEndPath(BR, EndN);
+}
 
-namespace {
+PathDiagnosticPiece*
+CFRefLeakReport::getEndPath(BugReporter& br, const ExplodedNode<GRState>* EndN){
   
-  //===-------------===//
-  // Bug Descriptions. //
-  //===-------------===//  
+  GRBugReporter& BR = cast<GRBugReporter>(br);
+  // Tell the BugReporter to report cases when the tracked symbol is
+  // assigned to different variables, etc.
+  cast<GRBugReporter>(BR).addNotableSymbol(Sym);
   
-  class VISIBILITY_HIDDEN CFRefBug : public BugType {
-  protected:
-    CFRefCount& TF;
-
-    CFRefBug(CFRefCount* tf, const char* name) 
-      : BugType(name, "Memory (Core Foundation/Objective-C)"), TF(*tf) {}    
-  public:
+  // We are reporting a leak.  Walk up the graph to get to the first node where
+  // the symbol appeared, and also get the first VarDecl that tracked object
+  // is stored to.
+  const ExplodedNode<GRState>* AllocNode = 0;
+  const MemRegion* FirstBinding = 0;
+  
+  llvm::tie(AllocNode, FirstBinding) =
+  GetAllocationSite(BR.getStateManager(), EndN, Sym);
+  
+  // Get the allocate site.  
+  assert(AllocNode);
+  Stmt* FirstStmt = cast<PostStmt>(AllocNode->getLocation()).getStmt();
+  
+  SourceManager& SMgr = BR.getContext().getSourceManager();
+  unsigned AllocLine =SMgr.getInstantiationLineNumber(FirstStmt->getLocStart());
+  
+  // Compute an actual location for the leak.  Sometimes a leak doesn't
+  // occur at an actual statement (e.g., transition between blocks; end
+  // of function) so we need to walk the graph and compute a real location.
+  const ExplodedNode<GRState>* LeakN = EndN;
+  PathDiagnosticLocation L;
+  
+  while (LeakN) {
+    ProgramPoint P = LeakN->getLocation();
     
-    CFRefCount& getTF() { return TF; }
-    const CFRefCount& getTF() const { return TF; }
-
-    // FIXME: Eventually remove.
-    virtual const char* getDescription() const = 0;
+    if (const PostStmt *PS = dyn_cast<PostStmt>(&P)) {
+      L = PathDiagnosticLocation(PS->getStmt()->getLocStart(), SMgr);
+      break;
+    }
+    else if (const BlockEdge *BE = dyn_cast<BlockEdge>(&P)) {
+      if (const Stmt* Term = BE->getSrc()->getTerminator()) {
+        L = PathDiagnosticLocation(Term->getLocStart(), SMgr);
+        break;
+      }
+    }
     
-    virtual bool isLeak() const { return false; }
-  };
+    LeakN = LeakN->succ_empty() ? 0 : *(LeakN->succ_begin());
+  }
   
-  class VISIBILITY_HIDDEN UseAfterRelease : public CFRefBug {
-  public:
-    UseAfterRelease(CFRefCount* tf)
-      : CFRefBug(tf, "Use-after-release") {}
-    
-    const char* getDescription() const {
-      return "Reference-counted object is used after it is released";
-    }    
-  };
+  if (!L.isValid()) {
+    L = PathDiagnosticLocation(
+                               BR.getStateManager().getCodeDecl().getBodyRBrace(BR.getContext()),
+                               SMgr);
+  }
   
-  class VISIBILITY_HIDDEN BadRelease : public CFRefBug {
-  public:
-    BadRelease(CFRefCount* tf) : CFRefBug(tf, "Bad release") {}
+  std::string sbuf;
+  llvm::raw_string_ostream os(sbuf);
+  
+  os << "Object allocated on line " << AllocLine;
+  
+  if (FirstBinding)
+    os << " and stored into '" << FirstBinding->getString() << '\'';  
+  
+  // Get the retain count.
+  const RefVal* RV = EndN->getState()->get<RefBindings>(Sym);
+  
+  if (RV->getKind() == RefVal::ErrorLeakReturned) {
+    // FIXME: Per comments in rdar://6320065, "create" only applies to CF
+    // ojbects.  Only "copy", "alloc", "retain" and "new" transfer ownership
+    // to the caller for NS objects.
+    ObjCMethodDecl& MD = cast<ObjCMethodDecl>(BR.getGraph().getCodeDecl());
+    os << " is returned from a method whose name ('"
+    << MD.getSelector().getAsString()
+    << "') does not contain 'copy' or otherwise starts with"
+    " 'new' or 'alloc'.  This violates the naming convention rules given"
+    " in the Memory Management Guide for Cocoa (object leaked).";
+  }
+  else
+    os << " is no longer referenced after this point and has a retain count of"
+    " +"
+    << RV->getCount() << " (object leaked).";
+  
+  return new PathDiagnosticEventPiece(L, os.str());
+}
 
-    const char* getDescription() const {
-      return "Incorrect decrement of the reference count of an "
-             "object is not owned at this point by the caller";
-    }
-  };
+
+CFRefLeakReport::CFRefLeakReport(CFRefBug& D, const CFRefCount &tf,
+                                 ExplodedNode<GRState> *n,
+                                 SymbolRef sym, GRExprEngine& Eng)
+: CFRefReport(D, tf, n, sym)
+{
   
-  class VISIBILITY_HIDDEN DeallocGC : public CFRefBug {
-  public:
-    DeallocGC(CFRefCount *tf) : CFRefBug(tf,
-                                         "-dealloc called while using GC") {}
-    
-    const char *getDescription() const {
-      return "-dealloc called while using GC";
-    }
-  };
+  // Most bug reports are cached at the location where they occured.
+  // With leaks, we want to unique them by the location where they were
+  // allocated, and only report a single path.  To do this, we need to find
+  // the allocation site of a piece of tracked memory, which we do via a
+  // call to GetAllocationSite.  This will walk the ExplodedGraph backwards.
+  // Note that this is *not* the trimmed graph; we are guaranteed, however,
+  // that all ancestor nodes that represent the allocation site have the
+  // same SourceLocation.
+  const ExplodedNode<GRState>* AllocNode = 0;
   
-  class VISIBILITY_HIDDEN DeallocNotOwned : public CFRefBug {
-  public:
-    DeallocNotOwned(CFRefCount *tf) : CFRefBug(tf,
-                            "-dealloc sent to non-exclusively owned object") {}
-    
-    const char *getDescription() const {
-      return "-dealloc sent to object that may be referenced elsewhere";
-    }
-  };  
+  llvm::tie(AllocNode, AllocBinding) =  // Set AllocBinding.
+  GetAllocationSite(Eng.getStateManager(), getEndNode(), getSymbol());
   
-  class VISIBILITY_HIDDEN Leak : public CFRefBug {
-    const bool isReturn;
-  protected:
-    Leak(CFRefCount* tf, const char* name, bool isRet)
-      : CFRefBug(tf, name), isReturn(isRet) {}
-  public:
-    
-    const char* getDescription() const { return ""; }
+  // Get the SourceLocation for the allocation site.
+  ProgramPoint P = AllocNode->getLocation();
+  AllocSite = cast<PostStmt>(P).getStmt()->getLocStart();
+  
+  // Fill in the description of the bug.
+  Description.clear();
+  llvm::raw_string_ostream os(Description);
+  SourceManager& SMgr = Eng.getContext().getSourceManager();
+  unsigned AllocLine = SMgr.getInstantiationLineNumber(AllocSite);
+  os << "Potential leak of object allocated on line " << AllocLine;
+  
+  // FIXME: AllocBinding doesn't get populated for RegionStore yet.
+  if (AllocBinding)
+    os << " and stored into '" << AllocBinding->getString() << '\'';
+}
+
+//===----------------------------------------------------------------------===//
+// Main checker logic.
+//===----------------------------------------------------------------------===//
+
+static inline ArgEffect GetArgE(RetainSummary* Summ, unsigned idx) {
+  return Summ ? Summ->getArg(idx) : MayEscape;
+}
+
+static inline RetEffect GetRetEffect(RetainSummary* Summ) {
+  return Summ ? Summ->getRetEffect() : RetEffect::MakeNoRet();
+}
+
+static inline ArgEffect GetReceiverE(RetainSummary* Summ) {
+  return Summ ? Summ->getReceiverEffect() : DoNothing;
+}
 
-    bool isLeak() const { return true; }
-  };
+static inline bool IsEndPath(RetainSummary* Summ) {
+  return Summ ? Summ->isEndPath() : false;
+}
+
+
+/// GetReturnType - Used to get the return type of a message expression or
+///  function call with the intention of affixing that type to a tracked symbol.
+///  While the the return type can be queried directly from RetEx, when
+///  invoking class methods we augment to the return type to be that of
+///  a pointer to the class (as opposed it just being id).
+static QualType GetReturnType(Expr* RetE, ASTContext& Ctx) {
+
+  QualType RetTy = RetE->getType();
+
+  // FIXME: We aren't handling id<...>.
+  const PointerType* PT = RetTy->getAsPointerType();
+  if (!PT)
+    return RetTy;
     
-  class VISIBILITY_HIDDEN LeakAtReturn : public Leak {
-  public:
-    LeakAtReturn(CFRefCount* tf, const char* name)
-      : Leak(tf, name, true) {}
-  };
-  
-  class VISIBILITY_HIDDEN LeakWithinFunction : public Leak {
-  public:
-    LeakWithinFunction(CFRefCount* tf, const char* name)
-      : Leak(tf, name, false) {}
-  };  
+  // If RetEx is not a message expression just return its type.
+  // If RetEx is a message expression, return its types if it is something
+  /// more specific than id.
   
-  //===---------===//
-  // Bug Reports.  //
-  //===---------===//
+  ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(RetE);
   
-  class VISIBILITY_HIDDEN CFRefReport : public RangedBugReport {
-  protected:
-    SymbolRef Sym;
-    const CFRefCount &TF;
-  public:
-    CFRefReport(CFRefBug& D, const CFRefCount &tf,
-                ExplodedNode<GRState> *n, SymbolRef sym)
-      : RangedBugReport(D, D.getDescription(), n), Sym(sym), TF(tf) {}
-        
-    virtual ~CFRefReport() {}
-    
-    CFRefBug& getBugType() {
-      return (CFRefBug&) RangedBugReport::getBugType();
-    }
-    const CFRefBug& getBugType() const {
-      return (const CFRefBug&) RangedBugReport::getBugType();
-    }
-    
-    virtual void getRanges(BugReporter& BR, const SourceRange*& beg,           
-                           const SourceRange*& end) {
-      
-      if (!getBugType().isLeak())
-        RangedBugReport::getRanges(BR, beg, end);
-      else
-        beg = end = 0;
-    }
-    
-    SymbolRef getSymbol() const { return Sym; }
-    
-    PathDiagnosticPiece* getEndPath(BugReporter& BR,
-                                    const ExplodedNode<GRState>* N);
-    
-    std::pair<const char**,const char**> getExtraDescriptiveText();
-    
-    PathDiagnosticPiece* VisitNode(const ExplodedNode<GRState>* N,
-                                   const ExplodedNode<GRState>* PrevN,
-                                   const ExplodedGraph<GRState>& G,
-                                   BugReporter& BR,
-                                   NodeResolver& NR);
-  };
+  if (!ME || !Ctx.isObjCIdStructType(PT->getPointeeType()))
+    return RetTy;
   
-  class VISIBILITY_HIDDEN CFRefLeakReport : public CFRefReport {
-    SourceLocation AllocSite;
-    const MemRegion* AllocBinding;
-  public:
-    CFRefLeakReport(CFRefBug& D, const CFRefCount &tf,
-                    ExplodedNode<GRState> *n, SymbolRef sym,
-                    GRExprEngine& Eng);
+  ObjCInterfaceDecl* D = ME->getClassInfo().first;  
 
-    PathDiagnosticPiece* getEndPath(BugReporter& BR,
-                                    const ExplodedNode<GRState>* N);
+  // At this point we know the return type of the message expression is id.
+  // If we have an ObjCInterceDecl, we know this is a call to a class method
+  // whose type we can resolve.  In such cases, promote the return type to
+  // Class*.  
+  return !D ? RetTy : Ctx.getPointerType(Ctx.getObjCInterfaceType(D));
+}
 
-    SourceLocation getLocation() const { return AllocSite; }
-  };  
-} // end anonymous namespace
 
-void CFRefCount::RegisterChecks(BugReporter& BR) {
-  useAfterRelease = new UseAfterRelease(this);
-  BR.Register(useAfterRelease);
-  
-  releaseNotOwned = new BadRelease(this);
-  BR.Register(releaseNotOwned);
-  
-  deallocGC = new DeallocGC(this);
-  BR.Register(deallocGC);
+void CFRefCount::EvalSummary(ExplodedNodeSet<GRState>& Dst,
+                             GRExprEngine& Eng,
+                             GRStmtNodeBuilder<GRState>& Builder,
+                             Expr* Ex,
+                             Expr* Receiver,
+                             RetainSummary* Summ,
+                             ExprIterator arg_beg, ExprIterator arg_end,
+                             ExplodedNode<GRState>* Pred) {
   
-  deallocNotOwned = new DeallocNotOwned(this);
-  BR.Register(deallocNotOwned);
+  // Get the state.
+  GRStateRef state(Builder.GetState(Pred), Eng.getStateManager());
+  ASTContext& Ctx = Eng.getStateManager().getContext();
+
+  // Evaluate the effect of the arguments.
+  RefVal::Kind hasErr = (RefVal::Kind) 0;
+  unsigned idx = 0;
+  Expr* ErrorExpr = NULL;
+  SymbolRef ErrorSym = 0;                                        
   
-  // First register "return" leaks.
-  const char* name = 0;
+  for (ExprIterator I = arg_beg; I != arg_end; ++I, ++idx) {    
+    SVal V = state.GetSValAsScalarOrLoc(*I);    
+    SymbolRef Sym = V.getAsLocSymbol();
+
+    if (Sym)
+      if (RefBindings::data_type* T = state.get<RefBindings>(Sym)) {
+        state = Update(state, Sym, *T, GetArgE(Summ, idx), hasErr);
+        if (hasErr) {
+          ErrorExpr = *I;
+          ErrorSym = Sym;
+          break;
+        }        
+        continue;
+      }
+
+    if (isa<Loc>(V)) {
+      if (loc::MemRegionVal* MR = dyn_cast<loc::MemRegionVal>(&V)) {
+        if (GetArgE(Summ, idx) == DoNothingByRef)
+          continue;
+        
+        // Invalidate the value of the variable passed by reference.
+        
+        // FIXME: Either this logic should also be replicated in GRSimpleVals
+        //  or should be pulled into a separate "constraint engine."
+        
+        // FIXME: We can have collisions on the conjured symbol if the
+        //  expression *I also creates conjured symbols.  We probably want
+        //  to identify conjured symbols by an expression pair: the enclosing
+        //  expression (the context) and the expression itself.  This should
+        //  disambiguate conjured symbols. 
+        
+        const TypedRegion* R = dyn_cast<TypedRegion>(MR->getRegion());
+
+        if (R) {          
+          // Is the invalidated variable something that we were tracking?
+          SymbolRef Sym = state.GetSValAsScalarOrLoc(R).getAsLocSymbol();
+          
+          // Remove any existing reference-count binding.
+          if (Sym) state = state.remove<RefBindings>(Sym);
+          
+          if (R->isBoundable(Ctx)) {
+            // Set the value of the variable to be a conjured symbol.
+            unsigned Count = Builder.getCurrentBlockCount();
+            QualType T = R->getRValueType(Ctx);
+          
+            if (Loc::IsLocType(T) || (T->isIntegerType() && T->isScalarType())){
+              ValueManager &ValMgr = Eng.getValueManager();
+              SVal V = ValMgr.getConjuredSymbolVal(*I, T, Count);
+              state = state.BindLoc(Loc::MakeVal(R), V);
+            }
+            else if (const RecordType *RT = T->getAsStructureType()) {
+              // Handle structs in a not so awesome way.  Here we just
+              // eagerly bind new symbols to the fields.  In reality we
+              // should have the store manager handle this.  The idea is just
+              // to prototype some basic functionality here.  All of this logic
+              // should one day soon just go away.
+              const RecordDecl *RD = RT->getDecl()->getDefinition(Ctx);
+              
+              // No record definition.  There is nothing we can do.
+              if (!RD)
+                continue;
+              
+              MemRegionManager &MRMgr = state.getManager().getRegionManager();
+              
+              // Iterate through the fields and construct new symbols.
+              for (RecordDecl::field_iterator FI=RD->field_begin(Ctx),
+                   FE=RD->field_end(Ctx); FI!=FE; ++FI) {
+                
+                // For now just handle scalar fields.
+                FieldDecl *FD = *FI;
+                QualType FT = FD->getType();
+                
+                if (Loc::IsLocType(FT) || 
+                    (FT->isIntegerType() && FT->isScalarType())) {                  
+                  const FieldRegion* FR = MRMgr.getFieldRegion(FD, R);
+                  ValueManager &ValMgr = Eng.getValueManager();
+                  SVal V = ValMgr.getConjuredSymbolVal(*I, FT, Count);
+                  state = state.BindLoc(Loc::MakeVal(FR), V);
+                }                
+              }
+            }
+            else {
+              // Just blast away other values.
+              state = state.BindLoc(*MR, UnknownVal());
+            }
+          }
+        }
+        else
+          state = state.BindLoc(*MR, UnknownVal());
+      }
+      else {
+        // Nuke all other arguments passed by reference.
+        state = state.Unbind(cast<Loc>(V));
+      }
+    }
+    else if (isa<nonloc::LocAsInteger>(V))
+      state = state.Unbind(cast<nonloc::LocAsInteger>(V).getLoc());
+  } 
   
-  if (isGCEnabled())
-    name = "Leak of returned object when using garbage collection";
-  else if (getLangOptions().getGCMode() == LangOptions::HybridGC)
-    name = "Leak of returned object when not using garbage collection (GC) in "
-           "dual GC/non-GC code";
-  else {
-    assert(getLangOptions().getGCMode() == LangOptions::NonGC);
-    name = "Leak of returned object";
+  // Evaluate the effect on the message receiver.  
+  if (!ErrorExpr && Receiver) {
+    SymbolRef Sym = state.GetSValAsScalarOrLoc(Receiver).getAsLocSymbol();
+    if (Sym) {
+      if (const RefVal* T = state.get<RefBindings>(Sym)) {
+        state = Update(state, Sym, *T, GetReceiverE(Summ), hasErr);
+        if (hasErr) {
+          ErrorExpr = Receiver;
+          ErrorSym = Sym;
+        }
+      }
+    }
   }
-  
-  leakAtReturn = new LeakAtReturn(this, name);
-  BR.Register(leakAtReturn);
-
-  // Second, register leaks within a function/method.
-  if (isGCEnabled())
-    name = "Leak of object when using garbage collection";  
-  else if (getLangOptions().getGCMode() == LangOptions::HybridGC)
-    name = "Leak of object when not using garbage collection (GC) in "
-           "dual GC/non-GC code";
-  else {
-    assert(getLangOptions().getGCMode() == LangOptions::NonGC);
-    name = "Leak";
+  
+  // Process any errors.  
+  if (hasErr) {
+    ProcessNonLeakError(Dst, Builder, Ex, ErrorExpr, Pred, state,
+                        hasErr, ErrorSym);
+    return;
   }
   
-  leakWithinFunction = new LeakWithinFunction(this, name);
-  BR.Register(leakWithinFunction);
+  // Consult the summary for the return value.  
+  RetEffect RE = GetRetEffect(Summ);
   
-  // Save the reference to the BugReporter.
-  this->BR = &BR;
-}
-
-static const char* Msgs[] = {
-  // GC only
-  "Code is compiled to only use garbage collection",    
-  // No GC.
-  "Code is compiled to use reference counts",
-  // Hybrid, with GC.
-  "Code is compiled to use either garbage collection (GC) or reference counts"
-  " (non-GC).  The bug occurs with GC enabled",  
-  // Hybrid, without GC
-  "Code is compiled to use either garbage collection (GC) or reference counts"
-  " (non-GC).  The bug occurs in non-GC mode"
-};
-
-std::pair<const char**,const char**> CFRefReport::getExtraDescriptiveText() {
-  CFRefCount& TF = static_cast<CFRefBug&>(getBugType()).getTF();
-
-  switch (TF.getLangOptions().getGCMode()) {
+  switch (RE.getKind()) {
     default:
-      assert(false);
-          
-    case LangOptions::GCOnly:
-      assert (TF.isGCEnabled());
-      return std::make_pair(&Msgs[0], &Msgs[0]+1);      
-
-    case LangOptions::NonGC:
-      assert (!TF.isGCEnabled());
-      return std::make_pair(&Msgs[1], &Msgs[1]+1);
-    
-    case LangOptions::HybridGC:
-      if (TF.isGCEnabled())
-        return std::make_pair(&Msgs[2], &Msgs[2]+1);
-      else
-        return std::make_pair(&Msgs[3], &Msgs[3]+1);
-  }
-}
-
-static inline bool contains(const llvm::SmallVectorImpl<ArgEffect>& V,
-                              ArgEffect X) {
-  for (llvm::SmallVectorImpl<ArgEffect>::const_iterator I=V.begin(), E=V.end();
-        I!=E; ++I)
-    if (*I == X) return true;
-  
-  return false;
-}
-
-PathDiagnosticPiece* CFRefReport::VisitNode(const ExplodedNode<GRState>* N,
-                                            const ExplodedNode<GRState>* PrevN,
-                                            const ExplodedGraph<GRState>& G,
-                                            BugReporter& BR,
-                                            NodeResolver& NR) {
-
-  // Check if the type state has changed.  
-  GRStateManager &StMgr = cast<GRBugReporter>(BR).getStateManager();
-  GRStateRef PrevSt(PrevN->getState(), StMgr);
-  GRStateRef CurrSt(N->getState(), StMgr);
-
-  const RefVal* CurrT = CurrSt.get<RefBindings>(Sym);  
-  if (!CurrT) return NULL;
-
-  const RefVal& CurrV = *CurrT;
-  const RefVal* PrevT = PrevSt.get<RefBindings>(Sym);
-
-  // Create a string buffer to constain all the useful things we want
-  // to tell the user.
-  std::string sbuf;
-  llvm::raw_string_ostream os(sbuf);
-
-  // This is the allocation site since the previous node had no bindings
-  // for this symbol.
-  if (!PrevT) {
-    Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
-
-    if (CallExpr *CE = dyn_cast<CallExpr>(S)) {
-      // Get the name of the callee (if it is available).
-      SVal X = CurrSt.GetSValAsScalarOrLoc(CE->getCallee());
-      if (const FunctionDecl* FD = X.getAsFunctionDecl())
-        os << "Call to function '" << FD->getNameAsString() <<'\'';
-      else
-        os << "function call";      
-    }          
-    else {
-      assert (isa<ObjCMessageExpr>(S));
-      os << "Method";
-    }
-    
-    if (CurrV.getObjKind() == RetEffect::CF) {
-      os << " returns a Core Foundation object with a ";
-    }
-    else {
-      assert (CurrV.getObjKind() == RetEffect::ObjC);
-      os << " returns an Objective-C object with a ";
+      assert (false && "Unhandled RetEffect."); break;
+      
+    case RetEffect::NoRet: {
+      
+      // Make up a symbol for the return value (not reference counted).
+      // FIXME: This is basically copy-and-paste from GRSimpleVals.  We 
+      //  should compose behavior, not copy it.
+      
+      // FIXME: We eventually should handle structs and other compound types
+      // that are returned by value.
+      
+      QualType T = Ex->getType();
+      
+      if (Loc::IsLocType(T) || (T->isIntegerType() && T->isScalarType())) {
+        unsigned Count = Builder.getCurrentBlockCount();
+        ValueManager &ValMgr = Eng.getValueManager();
+        SVal X = ValMgr.getConjuredSymbolVal(Ex, T, Count);
+        state = state.BindExpr(Ex, X, false);
+      }      
+      
+      break;
     }
-    
-    if (CurrV.isOwned()) {
-      os << "+1 retain count (owning reference).";
       
-      if (static_cast<CFRefBug&>(getBugType()).getTF().isGCEnabled()) {
-        assert(CurrV.getObjKind() == RetEffect::CF);
-        os << "  "
-          "Core Foundation objects are not automatically garbage collected.";
-      }
+    case RetEffect::Alias: {
+      unsigned idx = RE.getIndex();
+      assert (arg_end >= arg_beg);
+      assert (idx < (unsigned) (arg_end - arg_beg));
+      SVal V = state.GetSValAsScalarOrLoc(*(arg_beg+idx));
+      state = state.BindExpr(Ex, V, false);
+      break;
     }
-    else {
-      assert (CurrV.isNotOwned());
-      os << "+0 retain count (non-owning reference).";
+      
+    case RetEffect::ReceiverAlias: {
+      assert (Receiver);
+      SVal V = state.GetSValAsScalarOrLoc(Receiver);
+      state = state.BindExpr(Ex, V, false);
+      break;
     }
-    
-    PathDiagnosticLocation Pos(S, BR.getContext().getSourceManager());
-    return new PathDiagnosticEventPiece(Pos, os.str());
-  }
-  
-  // Gather up the effects that were performed on the object at this
-  // program point
-  llvm::SmallVector<ArgEffect, 2> AEffects;
-
-  if (const RetainSummary *Summ = TF.getSummaryOfNode(NR.getOriginalNode(N))) {
-    // We only have summaries attached to nodes after evaluating CallExpr and
-    // ObjCMessageExprs.
-    Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
-    
-    if (CallExpr *CE = dyn_cast<CallExpr>(S)) {
-      // Iterate through the parameter expressions and see if the symbol
-      // was ever passed as an argument.
-      unsigned i = 0;
       
-      for (CallExpr::arg_iterator AI=CE->arg_begin(), AE=CE->arg_end();
-           AI!=AE; ++AI, ++i) {
-        
-        // Retrieve the value of the argument.  Is it the symbol
-        // we are interested in?
-        if (CurrSt.GetSValAsScalarOrLoc(*AI).getAsLocSymbol() != Sym)
-          continue;
+    case RetEffect::OwnedAllocatedSymbol:
+    case RetEffect::OwnedSymbol: {
+      unsigned Count = Builder.getCurrentBlockCount();
+      ValueManager &ValMgr = Eng.getValueManager();      
+      SymbolRef Sym = ValMgr.getConjuredSymbol(Ex, Count);
+      QualType RetT = GetReturnType(Ex, ValMgr.getContext());      
+      state = state.set<RefBindings>(Sym, RefVal::makeOwned(RE.getObjKind(),
+                                                            RetT));
+      state = state.BindExpr(Ex, ValMgr.makeRegionVal(Sym), false);
 
-        // We have an argument.  Get the effect!
-        AEffects.push_back(Summ->getArg(i));
+      // FIXME: Add a flag to the checker where allocations are assumed to
+      // *not fail.
+#if 0
+      if (RE.getKind() == RetEffect::OwnedAllocatedSymbol) {
+        bool isFeasible;
+        state = state.Assume(loc::SymbolVal(Sym), true, isFeasible);
+        assert(isFeasible && "Cannot assume fresh symbol is non-null.");        
       }
+#endif
+      
+      break;
     }
-    else if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {      
-      if (Expr *receiver = ME->getReceiver())
-        if (CurrSt.GetSValAsScalarOrLoc(receiver).getAsLocSymbol() == Sym) {
-          // The symbol we are tracking is the receiver.
-          AEffects.push_back(Summ->getReceiverEffect());
-        }
+    
+    case RetEffect::GCNotOwnedSymbol:
+    case RetEffect::NotOwnedSymbol: {
+      unsigned Count = Builder.getCurrentBlockCount();
+      ValueManager &ValMgr = Eng.getValueManager();
+      SymbolRef Sym = ValMgr.getConjuredSymbol(Ex, Count);
+      QualType RetT = GetReturnType(Ex, ValMgr.getContext());      
+      state = state.set<RefBindings>(Sym, RefVal::makeNotOwned(RE.getObjKind(),
+                                                               RetT));
+      state = state.BindExpr(Ex, ValMgr.makeRegionVal(Sym), false);
+      break;
     }
   }
   
-  do {
-    // Get the previous type state.
-    RefVal PrevV = *PrevT;
-    
-    // Specially handle -dealloc.
-    if (!TF.isGCEnabled() && contains(AEffects, Dealloc)) {
-      // Determine if the object's reference count was pushed to zero.
-      assert(!(PrevV == CurrV) && "The typestate *must* have changed.");
-      // We may not have transitioned to 'release' if we hit an error.
-      // This case is handled elsewhere.
-      if (CurrV.getKind() == RefVal::Released) {
-        assert(CurrV.getCount() == 0);
-        os << "Object released by directly sending the '-dealloc' message";
-        break;
-      }
-    }
-
-    // Specially handle CFMakeCollectable and friends.
-    if (contains(AEffects, MakeCollectable)) {
-      // Get the name of the function.
-      Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
-      SVal X = CurrSt.GetSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee());
-      const FunctionDecl* FD = X.getAsFunctionDecl();
-      const std::string& FName = FD->getNameAsString();
-      
-      if (TF.isGCEnabled()) {
-        // Determine if the object's reference count was pushed to zero.
-        assert(!(PrevV == CurrV) && "The typestate *must* have changed.");
-        
-        os << "In GC mode a call to '" << FName
-           <<  "' decrements an object's retain count and registers the "
-               "object with the garbage collector. ";
-
-        if (CurrV.getKind() == RefVal::Released) {
-          assert(CurrV.getCount() == 0);
-          os << "Since it now has a 0 retain count the object can be "
-                "automatically collected by the garbage collector.";
-        }
-        else
-          os << "An object must have a 0 retain count to be garbage collected. "
-                "After this call its retain count is +" << CurrV.getCount()
-             << '.';
-      }
-      else 
-        os << "When GC is not enabled a call to '" << FName
-           << "' has no effect on its argument.";
+  // Generate a sink node if we are at the end of a path.
+  GRExprEngine::NodeTy *NewNode =
+    IsEndPath(Summ) ? Builder.MakeSinkNode(Dst, Ex, Pred, state)
+                    : Builder.MakeNode(Dst, Ex, Pred, state);
+  
+  // Annotate the edge with summary we used.
+  // FIXME: This assumes that we always use the same summary when generating
+  //  this node.
+  if (NewNode) SummaryLog[NewNode] = Summ;
+}
 
-      // Nothing more to say.
-      break;
-    }
 
-    // Determine if the typestate has changed.  
-    if (!(PrevV == CurrV))
-      switch (CurrV.getKind()) {
-      case RefVal::Owned:
-      case RefVal::NotOwned:
+void CFRefCount::EvalCall(ExplodedNodeSet<GRState>& Dst,
+                          GRExprEngine& Eng,
+                          GRStmtNodeBuilder<GRState>& Builder,
+                          CallExpr* CE, SVal L,
+                          ExplodedNode<GRState>* Pred) {
+  const FunctionDecl* FD = L.getAsFunctionDecl();
+  RetainSummary* Summ = !FD ? 0 
+                        : Summaries.getSummary(const_cast<FunctionDecl*>(FD));
+  
+  EvalSummary(Dst, Eng, Builder, CE, 0, Summ,
+              CE->arg_begin(), CE->arg_end(), Pred);
+}
+
+void CFRefCount::EvalObjCMessageExpr(ExplodedNodeSet<GRState>& Dst,
+                                     GRExprEngine& Eng,
+                                     GRStmtNodeBuilder<GRState>& Builder,
+                                     ObjCMessageExpr* ME,
+                                     ExplodedNode<GRState>* Pred) {  
+  RetainSummary* Summ;
+  
+  if (Expr* Receiver = ME->getReceiver()) {
+    // We need the type-information of the tracked receiver object
+    // Retrieve it from the state.
+    ObjCInterfaceDecl* ID = 0;
+
+    // FIXME: Wouldn't it be great if this code could be reduced?  It's just
+    // a chain of lookups.
+    // FIXME: Is this really working as expected?  There are cases where
+    //  we just use the 'ID' from the message expression.
+    const GRState* St = Builder.GetState(Pred);
+    SVal V = Eng.getStateManager().GetSValAsScalarOrLoc(St, Receiver);
 
-        if (PrevV.getCount() == CurrV.getCount())
-          return 0;
+    SymbolRef Sym = V.getAsLocSymbol();
+    if (Sym) {
+      if (const RefVal* T  = St->get<RefBindings>(Sym)) {
+        QualType Ty = T->getType();
         
-        if (PrevV.getCount() > CurrV.getCount())
-          os << "Reference count decremented.";
-        else
-          os << "Reference count incremented.";
-                  
-        if (unsigned Count = CurrV.getCount())
-          os << " The object now has a +" << Count << " retain count.";
+        if (const PointerType* PT = Ty->getAsPointerType()) {
+          QualType PointeeTy = PT->getPointeeType();
           
-        if (PrevV.getKind() == RefVal::Released) {
-          assert(TF.isGCEnabled() && CurrV.getCount() > 0);
-          os << " The object is not eligible for garbage collection until the "
-                "retain count reaches 0 again.";
+          if (ObjCInterfaceType* IT = dyn_cast<ObjCInterfaceType>(PointeeTy))
+            ID = IT->getDecl();
         }
-          
-        break;
-        
-      case RefVal::Released:
-        os << "Object released.";
-        break;
-        
-      case RefVal::ReturnedOwned:
-        os << "Object returned to caller as an owning reference (single retain "
-              "count transferred to caller).";
-        break;
-        
-      case RefVal::ReturnedNotOwned:
-        os << "Object returned to caller with a +0 (non-owning) retain count.";
-        break;
-
-      default:
-        return NULL;
       }
+    }
     
-    // Emit any remaining diagnostics for the argument effects (if any).
-    for (llvm::SmallVectorImpl<ArgEffect>::iterator I=AEffects.begin(),
-         E=AEffects.end(); I != E; ++I) {
+    // FIXME: The receiver could be a reference to a class, meaning that
+    //  we should use the class method.
+    Summ = Summaries.getInstanceMethodSummary(ME, ID);
+
+    // Special-case: are we sending a mesage to "self"?
+    //  This is a hack.  When we have full-IP this should be removed.
+    if (!Summ) {
+      ObjCMethodDecl* MD = 
+        dyn_cast<ObjCMethodDecl>(&Eng.getGraph().getCodeDecl());
       
-      // A bunch of things have alternate behavior under GC.
-      if (TF.isGCEnabled())
-        switch (*I) {
-          default: break;
-          case Autorelease:
-            os << "In GC mode an 'autorelease' has no effect.";
-            continue;
-          case IncRefMsg:
-            os << "In GC mode the 'retain' message has no effect.";
-            continue;
-          case DecRefMsg:
-            os << "In GC mode the 'release' message has no effect.";
-            continue;
+      if (MD) {
+        if (Expr* Receiver = ME->getReceiver()) {
+          SVal X = Eng.getStateManager().GetSValAsScalarOrLoc(St, Receiver);
+          if (loc::MemRegionVal* L = dyn_cast<loc::MemRegionVal>(&X))
+            if (L->getRegion() == Eng.getStateManager().getSelfRegion(St)) {
+              // Create a summmary where all of the arguments "StopTracking".
+              Summ = Summaries.getPersistentSummary(RetEffect::MakeNoRet(),
+                                                    DoNothing,
+                                                    StopTracking);
+            }
         }
+      }
     }
-  } while(0);
+  }
+  else
+    Summ = Summaries.getClassMethodSummary(ME);
 
-  if (os.str().empty())
-    return 0; // We have nothing to say!
-  
-  Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();    
-  PathDiagnosticLocation Pos(S, BR.getContext().getSourceManager());
-  PathDiagnosticPiece* P = new PathDiagnosticEventPiece(Pos, os.str());
-  
-  // Add the range by scanning the children of the statement for any bindings
-  // to Sym.
-  for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
-    if (Expr* Exp = dyn_cast_or_null<Expr>(*I))
-      if (CurrSt.GetSValAsScalarOrLoc(Exp).getAsLocSymbol() == Sym) {
-        P->addRange(Exp->getSourceRange());
-        break;
-      }
-  
-  return P;
+
+  EvalSummary(Dst, Eng, Builder, ME, ME->getReceiver(), Summ,
+              ME->arg_begin(), ME->arg_end(), Pred);
 }
 
 namespace {
-class VISIBILITY_HIDDEN FindUniqueBinding :
-  public StoreManager::BindingsHandler {
-    SymbolRef Sym;
-    const MemRegion* Binding;
-    bool First;
-    
-  public:
-    FindUniqueBinding(SymbolRef sym) : Sym(sym), Binding(0), First(true) {}
-    
-  bool HandleBinding(StoreManager& SMgr, Store store, const MemRegion* R,
-                     SVal val) {
+class VISIBILITY_HIDDEN StopTrackingCallback : public SymbolVisitor {
+  GRStateRef state;
+public:
+  StopTrackingCallback(GRStateRef st) : state(st) {}
+  GRStateRef getState() { return state; }
 
-    SymbolRef SymV = val.getAsSymbol();    
-    if (!SymV || SymV != Sym)
-      return true;
+  bool VisitSymbol(SymbolRef sym) {
+    state = state.remove<RefBindings>(sym);
+    return true;
+  }
+  
+  const GRState* getState() const { return state.getState(); }
+};
+} // end anonymous namespace
+  
+
+void CFRefCount::EvalBind(GRStmtNodeBuilderRef& B, SVal location, SVal val) {  
+  // Are we storing to something that causes the value to "escape"?  
+  bool escapes = false;
+  
+  // A value escapes in three possible cases (this may change):
+  //
+  // (1) we are binding to something that is not a memory region.
+  // (2) we are binding to a memregion that does not have stack storage
+  // (3) we are binding to a memregion with stack storage that the store
+  //     does not understand.  
+  GRStateRef state = B.getState();
+
+  if (!isa<loc::MemRegionVal>(location))
+    escapes = true;
+  else {
+    const MemRegion* R = cast<loc::MemRegionVal>(location).getRegion();
+    escapes = !B.getStateManager().hasStackStorage(R);
     
-    if (Binding) {
-      First = false;
-      return false;
+    if (!escapes) {
+      // To test (3), generate a new state with the binding removed.  If it is
+      // the same state, then it escapes (since the store cannot represent
+      // the binding).
+      escapes = (state == (state.BindLoc(cast<Loc>(location), UnknownVal())));
     }
-    else
-      Binding = R;
-    
-    return true;    
   }
-    
-  operator bool() { return First && Binding; }
-  const MemRegion* getRegion() { return Binding; }
-};  
+
+  // If our store can represent the binding and we aren't storing to something
+  // that doesn't have local storage then just return and have the simulation
+  // state continue as is.
+  if (!escapes)
+      return;
+
+  // Otherwise, find all symbols referenced by 'val' that we are tracking
+  // and stop tracking them.
+  B.MakeNode(state.scanReachableSymbols<StopTrackingCallback>(val).getState());
 }
 
-static std::pair<const ExplodedNode<GRState>*,const MemRegion*>
-GetAllocationSite(GRStateManager& StateMgr, const ExplodedNode<GRState>* N,
-                  SymbolRef Sym) {
+std::pair<GRStateRef,bool>
+CFRefCount::HandleSymbolDeath(GRStateManager& VMgr,
+                              const GRState* St, const Decl* CD,
+                              SymbolRef sid,
+                              RefVal V, bool& hasLeak) {
 
-  // Find both first node that referred to the tracked symbol and the
-  // memory location that value was store to.
-  const ExplodedNode<GRState>* Last = N;
-  const MemRegion* FirstBinding = 0;  
-  
-  while (N) {
-    const GRState* St = N->getState();
-    RefBindings B = St->get<RefBindings>();
-    
-    if (!B.lookup(Sym))
-      break;
+  // Any remaining leaks?
+  hasLeak = V.isOwned() || 
+            ((V.isNotOwned() || V.isReturnedOwned()) && V.getCount() > 0);
 
-    FindUniqueBinding FB(Sym);
-    StateMgr.iterBindings(St, FB);      
-    if (FB) FirstBinding = FB.getRegion();      
-    
-    Last = N;
-    N = N->pred_empty() ? NULL : *(N->pred_begin());    
-  }
+  GRStateRef state(St, VMgr);
   
-  return std::make_pair(Last, FirstBinding);
+  if (!hasLeak)
+    return std::make_pair(state.remove<RefBindings>(sid), false);
+  
+  return std::make_pair(state.set<RefBindings>(sid, V ^ RefVal::ErrorLeak),
+                        false);
 }
 
-PathDiagnosticPiece*
-CFRefReport::getEndPath(BugReporter& br, const ExplodedNode<GRState>* EndN) {
-  // Tell the BugReporter to report cases when the tracked symbol is
-  // assigned to different variables, etc.
-  GRBugReporter& BR = cast<GRBugReporter>(br);
-  cast<GRBugReporter>(BR).addNotableSymbol(Sym);
-  return RangedBugReport::getEndPath(BR, EndN);
-}
 
-PathDiagnosticPiece*
-CFRefLeakReport::getEndPath(BugReporter& br, const ExplodedNode<GRState>* EndN){
 
-  GRBugReporter& BR = cast<GRBugReporter>(br);
-  // Tell the BugReporter to report cases when the tracked symbol is
-  // assigned to different variables, etc.
-  cast<GRBugReporter>(BR).addNotableSymbol(Sym);
-    
-  // We are reporting a leak.  Walk up the graph to get to the first node where
-  // the symbol appeared, and also get the first VarDecl that tracked object
-  // is stored to.
-  const ExplodedNode<GRState>* AllocNode = 0;
-  const MemRegion* FirstBinding = 0;
+// Dead symbols.
 
-  llvm::tie(AllocNode, FirstBinding) =
-    GetAllocationSite(BR.getStateManager(), EndN, Sym);
-  
-  // Get the allocate site.  
-  assert(AllocNode);
-  Stmt* FirstStmt = cast<PostStmt>(AllocNode->getLocation()).getStmt();
 
-  SourceManager& SMgr = BR.getContext().getSourceManager();
-  unsigned AllocLine =SMgr.getInstantiationLineNumber(FirstStmt->getLocStart());
 
-  // Compute an actual location for the leak.  Sometimes a leak doesn't
-  // occur at an actual statement (e.g., transition between blocks; end
-  // of function) so we need to walk the graph and compute a real location.
-  const ExplodedNode<GRState>* LeakN = EndN;
-  PathDiagnosticLocation L;
+ // Return statements.
+
+void CFRefCount::EvalReturn(ExplodedNodeSet<GRState>& Dst,
+                            GRExprEngine& Eng,
+                            GRStmtNodeBuilder<GRState>& Builder,
+                            ReturnStmt* S,
+                            ExplodedNode<GRState>* Pred) {
+  
+  Expr* RetE = S->getRetValue();
+  if (!RetE)
+    return;
+  
+  GRStateRef state(Builder.GetState(Pred), Eng.getStateManager());
+  SymbolRef Sym = state.GetSValAsScalarOrLoc(RetE).getAsLocSymbol();
+  
+  if (!Sym)
+    return;
+
+  // Get the reference count binding (if any).
+  const RefVal* T = state.get<RefBindings>(Sym);
   
-  while (LeakN) {
-    ProgramPoint P = LeakN->getLocation();
-    
-    if (const PostStmt *PS = dyn_cast<PostStmt>(&P)) {
-      L = PathDiagnosticLocation(PS->getStmt()->getLocStart(), SMgr);
+  if (!T)
+    return;
+  
+  // Change the reference count.  
+  RefVal X = *T;  
+  
+  switch (X.getKind()) {      
+    case RefVal::Owned: { 
+      unsigned cnt = X.getCount();
+      assert (cnt > 0);
+      X = RefVal::makeReturnedOwned(cnt - 1);
       break;
     }
-    else if (const BlockEdge *BE = dyn_cast<BlockEdge>(&P)) {
-      if (const Stmt* Term = BE->getSrc()->getTerminator()) {
-        L = PathDiagnosticLocation(Term->getLocStart(), SMgr);
-        break;
-      }
+      
+    case RefVal::NotOwned: {
+      unsigned cnt = X.getCount();
+      X = cnt ? RefVal::makeReturnedOwned(cnt - 1)
+              : RefVal::makeReturnedNotOwned();
+      break;
     }
-
-    LeakN = LeakN->succ_empty() ? 0 : *(LeakN->succ_begin());
+      
+    default: 
+      return;
   }
+  
+  // Update the binding.
+  state = state.set<RefBindings>(Sym, X);
+  Pred = Builder.MakeNode(Dst, S, Pred, state);
+  
+  // Any leaks or other errors?
+  if (X.isReturnedOwned() && X.getCount() == 0) {
+    const Decl *CD = &Eng.getStateManager().getCodeDecl();
+    
+    if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(CD)) {
+      std::string s = MD->getSelector().getAsString();
+      // FIXME: Use method summary.
+      if (!followsReturnRule(s.c_str())) {
+        static int ReturnOwnLeakTag = 0;
+        state = state.set<RefBindings>(Sym, X ^ RefVal::ErrorLeakReturned);
 
-  if (!L.isValid()) {
-    L = PathDiagnosticLocation(
-          BR.getStateManager().getCodeDecl().getBodyRBrace(BR.getContext()),
-          SMgr);
+        // Generate an error node.
+        ExplodedNode<GRState> *N =
+          Builder.generateNode(PostStmt(S, &ReturnOwnLeakTag), state, Pred);
+        
+        CFRefLeakReport *report =
+          new CFRefLeakReport(*static_cast<CFRefBug*>(leakAtReturn), *this,
+                              N, Sym, Eng);
+        BR->EmitReport(report);
+      }
+    }
   }
+}
 
-  std::string sbuf;
-  llvm::raw_string_ostream os(sbuf);
-  
-  os << "Object allocated on line " << AllocLine;
+// Assumptions.
+
+const GRState* CFRefCount::EvalAssume(GRStateManager& VMgr,
+                                         const GRState* St,
+                                         SVal Cond, bool Assumption,
+                                         bool& isFeasible) {
+
+  // FIXME: We may add to the interface of EvalAssume the list of symbols
+  //  whose assumptions have changed.  For now we just iterate through the
+  //  bindings and check if any of the tracked symbols are NULL.  This isn't
+  //  too bad since the number of symbols we will track in practice are 
+  //  probably small and EvalAssume is only called at branches and a few
+  //  other places.
+  RefBindings B = St->get<RefBindings>();
   
-  if (FirstBinding)
-    os << " and stored into '" << FirstBinding->getString() << '\'';  
+  if (B.isEmpty())
+    return St;
   
-  // Get the retain count.
-  const RefVal* RV = EndN->getState()->get<RefBindings>(Sym);
+  bool changed = false;
   
-  if (RV->getKind() == RefVal::ErrorLeakReturned) {
-    // FIXME: Per comments in rdar://6320065, "create" only applies to CF
-    // ojbects.  Only "copy", "alloc", "retain" and "new" transfer ownership
-    // to the caller for NS objects.
-    ObjCMethodDecl& MD = cast<ObjCMethodDecl>(BR.getGraph().getCodeDecl());
-    os << " is returned from a method whose name ('"
-       << MD.getSelector().getAsString()
-       << "') does not contain 'copy' or otherwise starts with"
-          " 'new' or 'alloc'.  This violates the naming convention rules given"
-          " in the Memory Management Guide for Cocoa (object leaked).";
+  GRStateRef state(St, VMgr);
+  RefBindings::Factory& RefBFactory = state.get_context<RefBindings>();
+
+  for (RefBindings::iterator I=B.begin(), E=B.end(); I!=E; ++I) {    
+    // Check if the symbol is null (or equal to any constant).
+    // If this is the case, stop tracking the symbol.
+    if (VMgr.getSymVal(St, I.getKey())) {
+      changed = true;
+      B = RefBFactory.Remove(B, I.getKey());
+    }
   }
-  else
-    os << " is no longer referenced after this point and has a retain count of"
-          " +"
-       << RV->getCount() << " (object leaked).";
   
-  return new PathDiagnosticEventPiece(L, os.str());
+  if (changed)
+    state = state.set<RefBindings>(B);
+  
+  return state;
 }
 
+GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym,
+                              RefVal V, ArgEffect E,
+                              RefVal::Kind& hasErr) {
 
-CFRefLeakReport::CFRefLeakReport(CFRefBug& D, const CFRefCount &tf,
-                                 ExplodedNode<GRState> *n,
-                                 SymbolRef sym, GRExprEngine& Eng)
-  : CFRefReport(D, tf, n, sym)
-{
+  // In GC mode [... release] and [... retain] do nothing.
+  switch (E) {
+    default: break;
+    case IncRefMsg: E = isGCEnabled() ? DoNothing : IncRef; break;
+    case DecRefMsg: E = isGCEnabled() ? DoNothing : DecRef; break;
+    case MakeCollectable: E = isGCEnabled() ? DecRef : DoNothing; break;
+    case NewAutoreleasePool: E = isGCEnabled() ? DoNothing : 
+                                                 NewAutoreleasePool; break;
+  }
   
-  // Most bug reports are cached at the location where they occured.
-  // With leaks, we want to unique them by the location where they were
-  // allocated, and only report a single path.  To do this, we need to find
-  // the allocation site of a piece of tracked memory, which we do via a
-  // call to GetAllocationSite.  This will walk the ExplodedGraph backwards.
-  // Note that this is *not* the trimmed graph; we are guaranteed, however,
-  // that all ancestor nodes that represent the allocation site have the
-  // same SourceLocation.
-  const ExplodedNode<GRState>* AllocNode = 0;
+  // Handle all use-after-releases.
+  if (!isGCEnabled() && V.getKind() == RefVal::Released) {
+    V = V ^ RefVal::ErrorUseAfterRelease;
+    hasErr = V.getKind();
+    return state.set<RefBindings>(sym, V);
+  }      
   
-  llvm::tie(AllocNode, AllocBinding) =  // Set AllocBinding.
-    GetAllocationSite(Eng.getStateManager(), getEndNode(), getSymbol());
+  switch (E) {
+    default:
+      assert (false && "Unhandled CFRef transition.");
+      
+    case Dealloc:
+      // Any use of -dealloc in GC is *bad*.
+      if (isGCEnabled()) {
+        V = V ^ RefVal::ErrorDeallocGC;
+        hasErr = V.getKind();
+        break;
+      }
+      
+      switch (V.getKind()) {
+        default:
+          assert(false && "Invalid case.");
+        case RefVal::Owned:
+          // The object immediately transitions to the released state.
+          V = V ^ RefVal::Released;
+          V.clearCounts();
+          return state.set<RefBindings>(sym, V);
+        case RefVal::NotOwned:
+          V = V ^ RefVal::ErrorDeallocNotOwned;
+          hasErr = V.getKind();
+          break;
+      }      
+      break;
 
-  // Get the SourceLocation for the allocation site.
-  ProgramPoint P = AllocNode->getLocation();
-  AllocSite = cast<PostStmt>(P).getStmt()->getLocStart();
-    
-  // Fill in the description of the bug.
-  Description.clear();
-  llvm::raw_string_ostream os(Description);
-  SourceManager& SMgr = Eng.getContext().getSourceManager();
-  unsigned AllocLine = SMgr.getInstantiationLineNumber(AllocSite);
-  os << "Potential leak of object allocated on line " << AllocLine;
-  
-  // FIXME: AllocBinding doesn't get populated for RegionStore yet.
-  if (AllocBinding)
-    os << " and stored into '" << AllocBinding->getString() << '\'';
+    case NewAutoreleasePool:
+      assert(!isGCEnabled());
+      return state.add<AutoreleaseStack>(sym);
+      
+    case MayEscape:
+      if (V.getKind() == RefVal::Owned) {
+        V = V ^ RefVal::NotOwned;
+        break;
+      }
+
+      // Fall-through.
+      
+    case DoNothingByRef:
+    case DoNothing:
+      return state;
+
+    case Autorelease:
+      if (isGCEnabled())
+        return state;
+      
+      // Update the autorelease counts.
+      state = SendAutorelease(state, ARCountFactory, sym);
+
+      // Fall-through.
+      
+    case StopTracking:
+      return state.remove<RefBindings>(sym);
+
+    case IncRef:      
+      switch (V.getKind()) {
+        default:
+          assert(false);
+
+        case RefVal::Owned:
+        case RefVal::NotOwned:
+          V = V + 1;
+          break;          
+        case RefVal::Released:
+          // Non-GC cases are handled above.
+          assert(isGCEnabled());
+          V = (V ^ RefVal::Owned) + 1;
+          break;
+      }      
+      break;
+      
+    case SelfOwn:
+      V = V ^ RefVal::NotOwned;
+      // Fall-through.
+    case DecRef:
+      switch (V.getKind()) {
+        default:
+          // case 'RefVal::Released' handled above.
+          assert (false);
+
+        case RefVal::Owned:
+          assert(V.getCount() > 0);
+          if (V.getCount() == 1) V = V ^ RefVal::Released;
+          V = V - 1;
+          break;
+          
+        case RefVal::NotOwned:
+          if (V.getCount() > 0)
+            V = V - 1;
+          else {
+            V = V ^ RefVal::ErrorReleaseNotOwned;
+            hasErr = V.getKind();
+          }          
+          break;
+          
+        case RefVal::Released:
+          // Non-GC cases are handled above.
+          assert(isGCEnabled());
+          V = V ^ RefVal::ErrorUseAfterRelease;
+          hasErr = V.getKind();
+          break;  
+      }      
+      break;
+  }
+  return state.set<RefBindings>(sym, V);
 }
 
 //===----------------------------------------------------------------------===//
index 602c611f7f22a90c512f187c196ff942d1ca63de..dfaba6fae95bbfb04c79d272c9bf2267a6fd2d19 100644 (file)
@@ -438,6 +438,17 @@ void rdar6704930(unsigned char *s, unsigned int length) {
 - (void) myCFRelease:(id)__attribute__((objc_ownership_cfrelease))obj;
 @end
 
+@interface TestAttrHelper : NSObject
+- (NSString*) createString:(TestOwnershipAttr*)X;
+@end
+
+@implementation TestAttrHelper
+- (NSString*) createString:(TestOwnershipAttr*)X {
+  return [X returnsAnOwnedString]; // expected-warning{{leak}}
+}
+
+@end
+
 void test_attr_1(TestOwnershipAttr *X) {
   NSString *str = [X returnsAnOwnedString]; // expected-warning{{leak}}
 }