From: Ted Kremenek Date: Tue, 17 Mar 2009 19:42:23 +0000 (+0000) Subject: retain/release checker: Add support for reasoning about -dealloc. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f95e9fcffb8cffaf11c3ebd24f860b148694505a;p=clang retain/release checker: Add support for reasoning about -dealloc. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@67094 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Analysis/CFRefCount.cpp b/lib/Analysis/CFRefCount.cpp index b6c11c9cfc..7382e1a7ac 100644 --- a/lib/Analysis/CFRefCount.cpp +++ b/lib/Analysis/CFRefCount.cpp @@ -220,12 +220,9 @@ static bool isRefType(QualType RetTy, const char* prefix, namespace { /// ArgEffect is used to summarize a function/method call's effect on a /// particular argument. -enum ArgEffect { IncRefMsg, IncRef, - DecRefMsg, DecRef, - MakeCollectable, - DoNothing, DoNothingByRef, - StopTracking, MayEscape, SelfOwn, Autorelease, - NewAutoreleasePool }; +enum ArgEffect { Autorelease, Dealloc, DecRef, DecRefMsg, DoNothing, + DoNothingByRef, IncRefMsg, IncRef, MakeCollectable, MayEscape, + NewAutoreleasePool, SelfOwn, StopTracking }; /// ArgEffects summarizes the effects of a function/method call on all of /// its arguments. @@ -1155,6 +1152,10 @@ void RetainSummaryManager::InitializeMethodSummaries() { // Create the "drain" selector. Summ = getPersistentSummary(E, isGCEnabled() ? DoNothing : DecRef); addNSObjectMethSummary(GetNullarySelector("drain", Ctx), Summ); + + // Create the -dealloc summary. + Summ = getPersistentSummary(RetEffect::MakeNoRet(), Dealloc); + addNSObjectMethSummary(GetNullarySelector("dealloc", Ctx), Summ); // Create the "autorelease" selector. Summ = getPersistentSummary(E, Autorelease); @@ -1215,8 +1216,12 @@ public: Released, // Object has been released. ReturnedOwned, // Returned object passes ownership to caller. ReturnedNotOwned, // Return object does not pass ownership to caller. + ERROR_START, + ErrorDeallocNotOwned, // -dealloc called on non-owned object. + ErrorDeallocGC, // Calling -dealloc with GC enabled. ErrorUseAfterRelease, // Object used after released. ErrorReleaseNotOwned, // Release of an object that was not owned. + ERROR_LEAK_START, ErrorLeak, // A memory leak due to excessive reference counts. ErrorLeakReturned // A memory leak due to the returning method not having // the correct naming conventions. @@ -1239,14 +1244,16 @@ public: RetEffect::ObjKind getObjKind() const { return okind; } - unsigned getCount() const { return Cnt; } + unsigned getCount() const { return Cnt; } + void clearCounts() { Cnt = 0; } + QualType getType() const { return T; } // Useful predicates. - static bool isError(Kind k) { return k >= ErrorUseAfterRelease; } + static bool isError(Kind k) { return k >= ERROR_START; } - static bool isLeak(Kind k) { return k >= ErrorLeak; } + static bool isLeak(Kind k) { return k >= ERROR_LEAK_START; } bool isOwned() const { return getKind() == Owned; @@ -1269,8 +1276,6 @@ public: return isError(k) && !isLeak(k); } - // State creation: normal state. - static RefVal makeOwned(RetEffect::ObjKind o, QualType t, unsigned Count = 1) { return RefVal(Owned, o, Count, t); @@ -1306,7 +1311,7 @@ public: RefVal operator^(Kind k) const { return RefVal(k, getObjKind(), getCount(), getType()); } - + void Profile(llvm::FoldingSetNodeID& ID) const { ID.AddInteger((unsigned) kind); ID.AddInteger(Cnt); @@ -1353,6 +1358,14 @@ void RefVal::print(std::ostream& Out) const { case Released: Out << "Released"; break; + + case ErrorDeallocGC: + Out << "-dealloc (GC)"; + break; + + case ErrorDeallocNotOwned: + Out << "-dealloc (not-owned)"; + break; case ErrorLeak: Out << "Leaked"; @@ -1439,6 +1452,7 @@ private: ARCounts::Factory ARCountFactory; BugType *useAfterRelease, *releaseNotOwned; + BugType *deallocGC, *deallocNotOwned; BugType *leakWithinFunction, *leakAtReturn; BugReporter *BR; @@ -1459,7 +1473,8 @@ private: public: CFRefCount(ASTContext& Ctx, bool gcenabled, const LangOptions& lopts) : Summaries(Ctx, gcenabled), - LOpts(lopts), useAfterRelease(0), releaseNotOwned(0), + LOpts(lopts), useAfterRelease(0), releaseNotOwned(0), + deallocGC(0), deallocNotOwned(0), leakWithinFunction(0), leakAtReturn(0), BR(0) {} virtual ~CFRefCount() {} @@ -2150,9 +2165,39 @@ GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym, NewAutoreleasePool; break; } + // 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(); + 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; case NewAutoreleasePool: assert(!isGCEnabled()); @@ -2163,20 +2208,19 @@ GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym, V = V ^ RefVal::NotOwned; break; } + // Fall-through. case DoNothingByRef: case DoNothing: - if (!isGCEnabled() && V.getKind() == RefVal::Released) { - V = V ^ RefVal::ErrorUseAfterRelease; - hasErr = V.getKind(); - break; - } return state; case Autorelease: - if (isGCEnabled()) return state; - // Fall-through. + if (isGCEnabled()) + return state; + + // Fall-through. + case StopTracking: return state.remove(sym); @@ -2190,12 +2234,9 @@ GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym, V = V + 1; break; case RefVal::Released: - if (isGCEnabled()) - V = (V ^ RefVal::Owned) + 1; - else { - V = V ^ RefVal::ErrorUseAfterRelease; - hasErr = V.getKind(); - } + // Non-GC cases are handled above. + assert(isGCEnabled()); + V = (V ^ RefVal::Owned) + 1; break; } break; @@ -2206,6 +2247,7 @@ GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym, case DecRef: switch (V.getKind()) { default: + // case 'RefVal::Released' handled above. assert (false); case RefVal::Owned: @@ -2222,11 +2264,13 @@ GRStateRef CFRefCount::Update(GRStateRef state, SymbolRef sym, hasErr = V.getKind(); } break; - + case RefVal::Released: + // Non-GC cases are handled above. + assert(isGCEnabled()); V = V ^ RefVal::ErrorUseAfterRelease; hasErr = V.getKind(); - break; + break; } break; } @@ -2281,6 +2325,26 @@ namespace { } }; + 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"; + } + }; + + 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"; + } + }; + class VISIBILITY_HIDDEN Leak : public CFRefBug { const bool isReturn; protected: @@ -2372,6 +2436,12 @@ void CFRefCount::RegisterChecks(BugReporter& BR) { 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; @@ -2559,6 +2629,19 @@ PathDiagnosticPiece* CFRefReport::VisitNode(const ExplodedNode* N, 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)) { @@ -2737,10 +2820,9 @@ GetAllocationSite(GRStateManager& StateMgr, const ExplodedNode* N, PathDiagnosticPiece* CFRefReport::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. + GRBugReporter& BR = cast(br); cast(BR).addNotableSymbol(Sym); return RangedBugReport::getEndPath(BR, EndN); } @@ -3016,11 +3098,22 @@ void CFRefCount::ProcessNonLeakError(ExplodedNodeSet& Dst, CFRefBug *BT = 0; - if (hasErr == RefVal::ErrorUseAfterRelease) - BT = static_cast(useAfterRelease); - else { - assert(hasErr == RefVal::ErrorReleaseNotOwned); - BT = static_cast(releaseNotOwned); + switch (hasErr) { + default: + assert(false && "Unhandled error."); + return; + case RefVal::ErrorUseAfterRelease: + BT = static_cast(useAfterRelease); + break; + case RefVal::ErrorReleaseNotOwned: + BT = static_cast(releaseNotOwned); + break; + case RefVal::ErrorDeallocGC: + BT = static_cast(deallocGC); + break; + case RefVal::ErrorDeallocNotOwned: + BT = static_cast(deallocNotOwned); + break; } CFRefReport *report = new CFRefReport(*BT, *this, N, Sym);