From c887d13b07d72c8e67d1a73a82d3167e866f50e5 Mon Sep 17 00:00:00 2001 From: Ted Kremenek Date: Wed, 29 Apr 2009 18:50:19 +0000 Subject: [PATCH] retain/release checker: Hoist code for bug reports above transfer function logic (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 | 2380 ++++++++++++++++---------------- test/Analysis/retain-release.m | 11 + 2 files changed, 1208 insertions(+), 1183 deletions(-) diff --git a/lib/Analysis/CFRefCount.cpp b/lib/Analysis/CFRefCount.cpp index 149fe23465..77b23327e0 100644 --- a/lib/Analysis/CFRefCount.cpp +++ b/lib/Analysis/CFRefCount.cpp @@ -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(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& Dst, - GRExprEngine& Eng, - GRStmtNodeBuilder& Builder, - Expr* Ex, - Expr* Receiver, - RetainSummary* Summ, - ExprIterator arg_beg, ExprIterator arg_end, - ExplodedNode* 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(Sym)) { - state = Update(state, Sym, *T, GetArgE(Summ, idx), hasErr); - if (hasErr) { - ErrorExpr = *I; - ErrorSym = Sym; - break; - } - continue; - } - - if (isa(V)) { - if (loc::MemRegionVal* MR = dyn_cast(&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(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(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(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(V)) - state = state.Unbind(cast(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(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(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(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& Dst, - GRExprEngine& Eng, - GRStmtNodeBuilder& Builder, - CallExpr* CE, SVal L, - ExplodedNode* Pred) { - const FunctionDecl* FD = L.getAsFunctionDecl(); - RetainSummary* Summ = !FD ? 0 - : Summaries.getSummary(const_cast(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& Dst, - GRExprEngine& Eng, - GRStmtNodeBuilder& Builder, - ObjCMessageExpr* ME, - ExplodedNode* 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(Sym)) { - QualType Ty = T->getType(); - - if (const PointerType* PT = Ty->getAsPointerType()) { - QualType PointeeTy = PT->getPointeeType(); - - if (ObjCInterfaceType* IT = dyn_cast(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 *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(&Eng.getGraph().getCodeDecl()); - - if (MD) { - if (Expr* Receiver = ME->getReceiver()) { - SVal X = Eng.getStateManager().GetSValAsScalarOrLoc(St, Receiver); - if (loc::MemRegionVal* L = dyn_cast(&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(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* N); + + std::pair getExtraDescriptiveText(); + + PathDiagnosticPiece* VisitNode(const ExplodedNode* N, + const ExplodedNode* PrevN, + const ExplodedGraph& 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 *n, SymbolRef sym, + GRExprEngine& Eng); + + PathDiagnosticPiece* getEndPath(BugReporter& BR, + const ExplodedNode* 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(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(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(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(val).getState()); -} - -std::pair -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(CD)) { - std::string s = MD->getSelector().getAsString(); - if (!followsReturnRule(s.c_str())) { - hasLeak = true; - state = state.set(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(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(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 CFRefReport::getExtraDescriptiveText() { + CFRefCount& TF = static_cast(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& Dst, - GRExprEngine& Eng, - GRStmtNodeBuilder& Builder, - ReturnStmt* S, - ExplodedNode* Pred) { +static inline bool contains(const llvm::SmallVectorImpl& V, + ArgEffect X) { + for (llvm::SmallVectorImpl::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* N, + const ExplodedNode* PrevN, + const ExplodedGraph& 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(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(Sym); + const RefVal* CurrT = CurrSt.get(Sym); + if (!CurrT) return NULL; - if (!T) - return; + const RefVal& CurrV = *CurrT; + const RefVal* PrevT = PrevSt.get(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(N->getLocation()).getStmt(); + + if (CallExpr *CE = dyn_cast(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(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(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(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(); - - if (B.isEmpty()) - return St; - - bool changed = false; - - GRStateRef state(St, VMgr); - RefBindings::Factory& RefBFactory = state.get_context(); - - 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(B); + // Gather up the effects that were performed on the object at this + // program point + llvm::SmallVector 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(N->getLocation()).getStmt(); + + if (CallExpr *CE = dyn_cast(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(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(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(N->getLocation()).getStmt(); + SVal X = CurrSt.GetSValAsScalarOrLoc(cast(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(sym, V); - case RefVal::NotOwned: - V = V ^ RefVal::ErrorDeallocNotOwned; - hasErr = V.getKind(); - break; - } - break; - - case NewAutoreleasePool: - assert(!isGCEnabled()); - return state.add(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(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::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(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(*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 MemRegion*> +GetAllocationSite(GRStateManager& StateMgr, const ExplodedNode* N, + SymbolRef Sym) { + + // Find both first node that referred to the tracked symbol and the + // memory location that value was store to. + const ExplodedNode* Last = N; + const MemRegion* FirstBinding = 0; + + while (N) { + const GRState* St = N->getState(); + RefBindings B = St->get(); + + 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(sym, V); + + return std::make_pair(Last, FirstBinding); } -//===----------------------------------------------------------------------===// -// Error reporting. -//===----------------------------------------------------------------------===// +PathDiagnosticPiece* +CFRefReport::getEndPath(BugReporter& br, const ExplodedNode* EndN) { + // Tell the BugReporter to report cases when the tracked symbol is + // assigned to different variables, etc. + GRBugReporter& BR = cast(br); + cast(BR).addNotableSymbol(Sym); + return RangedBugReport::getEndPath(BR, EndN); +} -namespace { +PathDiagnosticPiece* +CFRefLeakReport::getEndPath(BugReporter& br, const ExplodedNode* EndN){ - //===-------------===// - // Bug Descriptions. // - //===-------------===// + GRBugReporter& BR = cast(br); + // Tell the BugReporter to report cases when the tracked symbol is + // assigned to different variables, etc. + cast(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* AllocNode = 0; + const MemRegion* FirstBinding = 0; + + llvm::tie(AllocNode, FirstBinding) = + GetAllocationSite(BR.getStateManager(), EndN, Sym); + + // Get the allocate site. + assert(AllocNode); + Stmt* FirstStmt = cast(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* 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(&P)) { + L = PathDiagnosticLocation(PS->getStmt()->getLocStart(), SMgr); + break; + } + else if (const BlockEdge *BE = dyn_cast(&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(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(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 *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* 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(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(RetE); - class VISIBILITY_HIDDEN CFRefReport : public RangedBugReport { - protected: - SymbolRef Sym; - const CFRefCount &TF; - public: - CFRefReport(CFRefBug& D, const CFRefCount &tf, - ExplodedNode *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* N); - - std::pair getExtraDescriptiveText(); - - PathDiagnosticPiece* VisitNode(const ExplodedNode* N, - const ExplodedNode* PrevN, - const ExplodedGraph& 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 *n, SymbolRef sym, - GRExprEngine& Eng); + ObjCInterfaceDecl* D = ME->getClassInfo().first; - PathDiagnosticPiece* getEndPath(BugReporter& BR, - const ExplodedNode* 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& Dst, + GRExprEngine& Eng, + GRStmtNodeBuilder& Builder, + Expr* Ex, + Expr* Receiver, + RetainSummary* Summ, + ExprIterator arg_beg, ExprIterator arg_end, + ExplodedNode* 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(Sym)) { + state = Update(state, Sym, *T, GetArgE(Summ, idx), hasErr); + if (hasErr) { + ErrorExpr = *I; + ErrorSym = Sym; + break; + } + continue; + } + + if (isa(V)) { + if (loc::MemRegionVal* MR = dyn_cast(&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(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(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(V)); + } + } + else if (isa(V)) + state = state.Unbind(cast(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(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 CFRefReport::getExtraDescriptiveText() { - CFRefCount& TF = static_cast(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& V, - ArgEffect X) { - for (llvm::SmallVectorImpl::const_iterator I=V.begin(), E=V.end(); - I!=E; ++I) - if (*I == X) return true; - - return false; -} - -PathDiagnosticPiece* CFRefReport::VisitNode(const ExplodedNode* N, - const ExplodedNode* PrevN, - const ExplodedGraph& G, - BugReporter& BR, - NodeResolver& NR) { - - // Check if the type state has changed. - GRStateManager &StMgr = cast(BR).getStateManager(); - GRStateRef PrevSt(PrevN->getState(), StMgr); - GRStateRef CurrSt(N->getState(), StMgr); - - const RefVal* CurrT = CurrSt.get(Sym); - if (!CurrT) return NULL; - - const RefVal& CurrV = *CurrT; - const RefVal* PrevT = PrevSt.get(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(N->getLocation()).getStmt(); - - if (CallExpr *CE = dyn_cast(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(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(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 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(N->getLocation()).getStmt(); - - if (CallExpr *CE = dyn_cast(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(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(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(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(N->getLocation()).getStmt(); - SVal X = CurrSt.GetSValAsScalarOrLoc(cast(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& Dst, + GRExprEngine& Eng, + GRStmtNodeBuilder& Builder, + CallExpr* CE, SVal L, + ExplodedNode* Pred) { + const FunctionDecl* FD = L.getAsFunctionDecl(); + RetainSummary* Summ = !FD ? 0 + : Summaries.getSummary(const_cast(FD)); + + EvalSummary(Dst, Eng, Builder, CE, 0, Summ, + CE->arg_begin(), CE->arg_end(), Pred); +} + +void CFRefCount::EvalObjCMessageExpr(ExplodedNodeSet& Dst, + GRExprEngine& Eng, + GRStmtNodeBuilder& Builder, + ObjCMessageExpr* ME, + ExplodedNode* 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(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(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::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(&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(&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(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(*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(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(location)) + escapes = true; + else { + const MemRegion* R = cast(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(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(val).getState()); } -static std::pair*,const MemRegion*> -GetAllocationSite(GRStateManager& StateMgr, const ExplodedNode* N, - SymbolRef Sym) { +std::pair +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* Last = N; - const MemRegion* FirstBinding = 0; - - while (N) { - const GRState* St = N->getState(); - RefBindings B = St->get(); - - 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(sid), false); + + return std::make_pair(state.set(sid, V ^ RefVal::ErrorLeak), + false); } -PathDiagnosticPiece* -CFRefReport::getEndPath(BugReporter& br, const ExplodedNode* EndN) { - // Tell the BugReporter to report cases when the tracked symbol is - // assigned to different variables, etc. - GRBugReporter& BR = cast(br); - cast(BR).addNotableSymbol(Sym); - return RangedBugReport::getEndPath(BR, EndN); -} -PathDiagnosticPiece* -CFRefLeakReport::getEndPath(BugReporter& br, const ExplodedNode* EndN){ - GRBugReporter& BR = cast(br); - // Tell the BugReporter to report cases when the tracked symbol is - // assigned to different variables, etc. - cast(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* 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(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* LeakN = EndN; - PathDiagnosticLocation L; + // Return statements. + +void CFRefCount::EvalReturn(ExplodedNodeSet& Dst, + GRExprEngine& Eng, + GRStmtNodeBuilder& Builder, + ReturnStmt* S, + ExplodedNode* 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(Sym); - while (LeakN) { - ProgramPoint P = LeakN->getLocation(); - - if (const PostStmt *PS = dyn_cast(&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(&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(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(CD)) { + std::string s = MD->getSelector().getAsString(); + // FIXME: Use method summary. + if (!followsReturnRule(s.c_str())) { + static int ReturnOwnLeakTag = 0; + state = state.set(Sym, X ^ RefVal::ErrorLeakReturned); - if (!L.isValid()) { - L = PathDiagnosticLocation( - BR.getStateManager().getCodeDecl().getBodyRBrace(BR.getContext()), - SMgr); + // Generate an error node. + ExplodedNode *N = + Builder.generateNode(PostStmt(S, &ReturnOwnLeakTag), state, Pred); + + CFRefLeakReport *report = + new CFRefLeakReport(*static_cast(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(); - if (FirstBinding) - os << " and stored into '" << FirstBinding->getString() << '\''; + if (B.isEmpty()) + return St; - // Get the retain count. - const RefVal* RV = EndN->getState()->get(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(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(); + + 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(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 *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* AllocNode = 0; + // Handle all use-after-releases. + if (!isGCEnabled() && V.getKind() == RefVal::Released) { + V = V ^ RefVal::ErrorUseAfterRelease; + hasErr = V.getKind(); + return state.set(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(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(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(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(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(sym, V); } //===----------------------------------------------------------------------===// diff --git a/test/Analysis/retain-release.m b/test/Analysis/retain-release.m index 602c611f7f..dfaba6fae9 100644 --- a/test/Analysis/retain-release.m +++ b/test/Analysis/retain-release.m @@ -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}} } -- 2.40.0