diff options
author | Jordy Rose <jediknil@belkadan.com> | 2011-08-22 23:48:23 +0000 |
---|---|---|
committer | Jordy Rose <jediknil@belkadan.com> | 2011-08-22 23:48:23 +0000 |
commit | 294396b9f2a2f4ffee6b7ed5e61211fde50b6554 (patch) | |
tree | 20f4d3c093bb8be0a42bb12106684d69ddfacda2 /lib/StaticAnalyzer/Core/CFRefCount.cpp | |
parent | 7bbd166c0e7644e56257537fc16082bf270f8dfb (diff) |
[analyzer] Migrate the handling of retain-count-related RetEffects and ArgEffects from CFRefCount to RetainReleaseChecker. No intended functionality change.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@138309 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/StaticAnalyzer/Core/CFRefCount.cpp')
-rw-r--r-- | lib/StaticAnalyzer/Core/CFRefCount.cpp | 503 |
1 files changed, 292 insertions, 211 deletions
diff --git a/lib/StaticAnalyzer/Core/CFRefCount.cpp b/lib/StaticAnalyzer/Core/CFRefCount.cpp index e1cf34c2db..f71aba6bf8 100644 --- a/lib/StaticAnalyzer/Core/CFRefCount.cpp +++ b/lib/StaticAnalyzer/Core/CFRefCount.cpp @@ -1633,15 +1633,6 @@ public: ArgEffect E, RefVal::Kind& hasErr); - void ProcessNonLeakError(ExplodedNodeSet &Dst, - StmtNodeBuilder& Builder, - const Expr *NodeExpr, - SourceRange ErrorRange, - ExplodedNode *Pred, - const ProgramState *St, - RefVal::Kind hasErr, - SymbolRef Sym); - const ProgramState *HandleSymbolDeath(const ProgramState * state, SymbolRef sid, RefVal V, @@ -2547,11 +2538,6 @@ void CFRefCount::evalSummary(ExplodedNodeSet &Dst, ExplodedNode *Pred, const ProgramState *state) { - // Evaluate the effect of the arguments. - RefVal::Kind hasErr = (RefVal::Kind) 0; - SourceRange ErrorRange; - SymbolRef ErrorSym = 0; - SmallVector<const MemRegion*, 10> RegionsToInvalidate; // Use RAII to make sure the whitelist is properly cleared. @@ -2579,80 +2565,62 @@ void CFRefCount::evalSummary(ExplodedNodeSet &Dst, for (unsigned idx = 0, e = callOrMsg.getNumArgs(); idx != e; ++idx) { SVal V = callOrMsg.getArgSValAsScalarOrLoc(idx); - SymbolRef Sym = V.getAsLocSymbol(); - if (Sym) - if (RefBindings::data_type* T = state->get<RefBindings>(Sym)) { - WhitelistedSymbols.insert(Sym); - state = Update(state, Sym, *T, Summ.getArg(idx), hasErr); - if (hasErr) { - ErrorRange = callOrMsg.getArgSourceRange(idx); - ErrorSym = Sym; - break; - } - } + // If we are passing a location wrapped as an integer, unwrap it and + // invalidate the values referred by the location. + if (nonloc::LocAsInteger *Wrapped = dyn_cast<nonloc::LocAsInteger>(&V)) + V = Wrapped->getLoc(); + else if (!isa<Loc>(V)) + continue; - tryAgain: - if (isa<Loc>(V)) { - if (loc::MemRegionVal* MR = dyn_cast<loc::MemRegionVal>(&V)) { - if (Summ.getArg(idx) == DoNothingByRef) - continue; + if (SymbolRef Sym = V.getAsLocSymbol()) + if (state->get<RefBindings>(Sym)) + WhitelistedSymbols.insert(Sym); - // Invalidate the value of the variable passed by reference. - const MemRegion *R = MR->getRegion(); - - // Are we dealing with an ElementRegion? If the element type is - // a basic integer type (e.g., char, int) and the underying region - // is a variable region then strip off the ElementRegion. - // FIXME: We really need to think about this for the general case - // as sometimes we are reasoning about arrays and other times - // about (char*), etc., is just a form of passing raw bytes. - // e.g., void *p = alloca(); foo((char*)p); - if (const ElementRegion *ER = dyn_cast<ElementRegion>(R)) { - // Checking for 'integral type' is probably too promiscuous, but - // we'll leave it in for now until we have a systematic way of - // handling all of these cases. Eventually we need to come up - // with an interface to StoreManager so that this logic can be - // approriately delegated to the respective StoreManagers while - // still allowing us to do checker-specific logic (e.g., - // invalidating reference counts), probably via callbacks. - if (ER->getElementType()->isIntegralOrEnumerationType()) { - const MemRegion *superReg = ER->getSuperRegion(); - if (isa<VarRegion>(superReg) || isa<FieldRegion>(superReg) || - isa<ObjCIvarRegion>(superReg)) - R = cast<TypedRegion>(superReg); - } - // FIXME: What about layers of ElementRegions? + if (const MemRegion *R = V.getAsRegion()) { + // Invalidate the value of the variable passed by reference. + + // Are we dealing with an ElementRegion? If the element type is + // a basic integer type (e.g., char, int) and the underying region + // is a variable region then strip off the ElementRegion. + // FIXME: We really need to think about this for the general case + // as sometimes we are reasoning about arrays and other times + // about (char*), etc., is just a form of passing raw bytes. + // e.g., void *p = alloca(); foo((char*)p); + if (const ElementRegion *ER = dyn_cast<ElementRegion>(R)) { + // Checking for 'integral type' is probably too promiscuous, but + // we'll leave it in for now until we have a systematic way of + // handling all of these cases. Eventually we need to come up + // with an interface to StoreManager so that this logic can be + // approriately delegated to the respective StoreManagers while + // still allowing us to do checker-specific logic (e.g., + // invalidating reference counts), probably via callbacks. + if (ER->getElementType()->isIntegralOrEnumerationType()) { + const MemRegion *superReg = ER->getSuperRegion(); + if (isa<VarRegion>(superReg) || isa<FieldRegion>(superReg) || + isa<ObjCIvarRegion>(superReg)) + R = cast<TypedRegion>(superReg); } - - // Mark this region for invalidation. We batch invalidate regions - // below for efficiency. - RegionsToInvalidate.push_back(R); - continue; - } - else { - // Nuke all other arguments passed by reference. - // FIXME: is this necessary or correct? This handles the non-Region - // cases. Is it ever valid to store to these? - state = state->unbindLoc(cast<Loc>(V)); + // FIXME: What about layers of ElementRegions? } - } - else if (isa<nonloc::LocAsInteger>(V)) { - // If we are passing a location wrapped as an integer, unwrap it and - // invalidate the values referred by the location. - V = cast<nonloc::LocAsInteger>(V).getLoc(); - goto tryAgain; + + // Mark this region for invalidation. We batch invalidate regions + // below for efficiency. + RegionsToInvalidate.push_back(R); + } else { + // Nuke all other arguments passed by reference. + // FIXME: is this necessary or correct? This handles the non-Region + // cases. Is it ever valid to store to these? + state = state->unbindLoc(cast<Loc>(V)); } } // Block calls result in all captured values passed-via-reference to be // invalidated. - if (const BlockDataRegion *BR = dyn_cast_or_null<BlockDataRegion>(Callee)) { + if (const BlockDataRegion *BR = dyn_cast_or_null<BlockDataRegion>(Callee)) RegionsToInvalidate.push_back(BR); - } - // Invalidate regions we designed for invalidation use the batch invalidation - // API. + // Invalidate designated regions using the batch invalidation API. // FIXME: We can have collisions on the conjured symbol if the // expression *I also creates conjured symbols. We probably want @@ -2673,92 +2641,7 @@ void CFRefCount::evalSummary(ExplodedNodeSet &Dst, /* invalidateGlobals = */ Eng.doesInvalidateGlobals(callOrMsg)); - // Evaluate the effect on the message receiver. - if (!ErrorRange.isValid() && Receiver) { - SymbolRef Sym = Receiver.getSValAsScalarOrLoc(state).getAsLocSymbol(); - if (Sym) { - if (const RefVal* T = state->get<RefBindings>(Sym)) { - state = Update(state, Sym, *T, Summ.getReceiverEffect(), hasErr); - if (hasErr) { - ErrorRange = Receiver.getSourceRange(); - ErrorSym = Sym; - } - } - } - } - - // Process any errors. - if (hasErr) { - ProcessNonLeakError(Dst, Builder, Ex, ErrorRange, Pred, state, - hasErr, ErrorSym); - return; - } - - // Consult the summary for the return value. - RetEffect RE = Summ.getRetEffect(); - - if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) { - bool found = false; - if (Receiver) { - SVal V = Receiver.getSValAsScalarOrLoc(state); - if (SymbolRef Sym = V.getAsLocSymbol()) - if (state->get<RefBindings>(Sym)) { - found = true; - RE = Summaries.getObjAllocRetEffect(); - } - } // FIXME: Otherwise, this is a send-to-super instance message. - if (!found) - RE = RetEffect::MakeNoRet(); - } - - switch (RE.getKind()) { - default: - llvm_unreachable("Unhandled RetEffect."); break; - - case RetEffect::NoRet: - // No work necessary. - break; - - case RetEffect::OwnedAllocatedSymbol: - case RetEffect::OwnedSymbol: - if (SymbolRef Sym = state->getSVal(Ex).getAsSymbol()) { - // Use the result type from callOrMsg as it automatically adjusts - // for methods/functions that return references. - QualType ResultTy = callOrMsg.getResultType(Eng.getContext()); - state = state->set<RefBindings>(Sym, RefVal::makeOwned(RE.getObjKind(), - ResultTy)); - } - - // FIXME: Add a flag to the checker where allocations are assumed to - // *not* fail. (The code below is out-of-date, though.) -#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; - - case RetEffect::GCNotOwnedSymbol: - case RetEffect::ARCNotOwnedSymbol: - case RetEffect::NotOwnedSymbol: { - if (SymbolRef Sym = state->getSVal(Ex).getAsSymbol()) { - // Use GetReturnType in order to give [NSFoo alloc] the type NSFoo *. - QualType ResultTy = GetReturnType(Ex, Eng.getContext()); - state = - state->set<RefBindings>(Sym, RefVal::makeNotOwned(RE.getObjKind(), - ResultTy)); - } - break; - } - } - - ExplodedNode *NewNode = Builder.MakeNode(Dst, Ex, Pred, state); - - // Annotate the edge with summary we used. - if (NewNode) SummaryLog[NewNode] = &Summ; + Builder.MakeNode(Dst, Ex, Pred, state); } @@ -3320,44 +3203,6 @@ void CFRefCount::evalDeadSymbols(ExplodedNodeSet &Dst, Builder.MakeNode(Dst, S, Pred, state); } -void CFRefCount::ProcessNonLeakError(ExplodedNodeSet &Dst, - StmtNodeBuilder& Builder, - const Expr *NodeExpr, - SourceRange ErrorRange, - ExplodedNode *Pred, - const ProgramState *St, - RefVal::Kind hasErr, SymbolRef Sym) { - Builder.BuildSinks = true; - ExplodedNode *N = Builder.MakeNode(Dst, NodeExpr, Pred, St); - - if (!N) - return; - - CFRefBug *BT = 0; - - switch (hasErr) { - default: - assert(false && "Unhandled error."); - return; - case RefVal::ErrorUseAfterRelease: - BT = static_cast<CFRefBug*>(useAfterRelease); - break; - case RefVal::ErrorReleaseNotOwned: - BT = static_cast<CFRefBug*>(releaseNotOwned); - break; - case RefVal::ErrorDeallocGC: - BT = static_cast<CFRefBug*>(deallocGC); - break; - case RefVal::ErrorDeallocNotOwned: - BT = static_cast<CFRefBug*>(deallocNotOwned); - break; - } - - CFRefReport *report = new CFRefReport(*BT, *this, N, Sym); - report->addRange(ErrorRange); - BR->EmitReport(report); -} - //===----------------------------------------------------------------------===// // Pieces of the retain/release checker implemented using a CheckerVisitor. // More pieces of the retain/release checker will be migrated to this interface @@ -3369,6 +3214,8 @@ class RetainReleaseChecker : public Checker< check::Bind, check::PostStmt<BlockExpr>, check::PostStmt<CastExpr>, + check::PostStmt<CallExpr>, + check::PostObjCMessage, check::RegionChanges, eval::Assume, eval::Call > { @@ -3377,6 +3224,11 @@ public: void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const; + void checkSummary(const RetainSummary &Summ, const CallOrObjCMessage &Call, + InstanceReceiver Receiver, CheckerContext &C) const; + bool evalCall(const CallExpr *CE, CheckerContext &C) const; const ProgramState *evalAssume(const ProgramState *state, SVal Cond, @@ -3390,6 +3242,11 @@ public: bool wantsRegionChangeUpdate(const ProgramState *state) const { return true; } + + void processNonLeakError(const ProgramState *St, SourceRange ErrorRange, + RefVal::Kind ErrorKind, SymbolRef Sym, + CheckerContext &C) const; + }; } // end anonymous namespace @@ -3579,6 +3436,224 @@ void RetainReleaseChecker::checkPostStmt(const CastExpr *CE, C.generateNode(state); } +void RetainReleaseChecker::checkPostStmt(const CallExpr *CE, + CheckerContext &C) const { + // FIXME: This goes away once the RetainSummaryManager moves to the checker. + CFRefCount &TF = static_cast<CFRefCount&>(C.getEngine().getTF()); + RetainSummaryManager &Summaries = TF.Summaries; + + // Get the callee. + const ProgramState *state = C.getState(); + const Expr *Callee = CE->getCallee(); + SVal L = state->getSVal(Callee); + + RetainSummary *Summ = 0; + + // FIXME: Better support for blocks. For now we stop tracking anything + // that is passed to blocks. + // FIXME: Need to handle variables that are "captured" by the block. + if (dyn_cast_or_null<BlockDataRegion>(L.getAsRegion())) { + Summ = Summaries.getPersistentStopSummary(); + } else if (const FunctionDecl *FD = L.getAsFunctionDecl()) { + Summ = Summaries.getSummary(FD); + } else if (const CXXMemberCallExpr *me = dyn_cast<CXXMemberCallExpr>(CE)) { + if (const CXXMethodDecl *MD = me->getMethodDecl()) + Summ = Summaries.getSummary(MD); + } + + // If we didn't get a summary, this function doesn't affect retain counts. + if (!Summ) + return; + + checkSummary(*Summ, CallOrObjCMessage(CE, state), InstanceReceiver(), C); +} + +void RetainReleaseChecker::checkPostObjCMessage(const ObjCMessage &Msg, + CheckerContext &C) const { + // FIXME: This goes away once the RetainSummaryManager moves to the checker. + CFRefCount &TF = static_cast<CFRefCount&>(C.getEngine().getTF()); + RetainSummaryManager &Summaries = TF.Summaries; + + const ProgramState *state = C.getState(); + ExplodedNode *Pred = C.getPredecessor(); + + RetainSummary *Summ; + if (Msg.isInstanceMessage()) { + const LocationContext *LC = Pred->getLocationContext(); + Summ = Summaries.getInstanceMethodSummary(Msg, state, LC); + } else { + Summ = Summaries.getClassMethodSummary(Msg); + } + + // If we didn't get a summary, this message doesn't affect retain counts. + if (!Summ) + return; + + checkSummary(*Summ, CallOrObjCMessage(Msg, state), + InstanceReceiver(Msg, Pred->getLocationContext()), C); +} + +void RetainReleaseChecker::checkSummary(const RetainSummary &Summ, + const CallOrObjCMessage &CallOrMsg, + InstanceReceiver Receiver, + CheckerContext &C) const { + // FIXME: This goes away once the Update() method moves to the checker. + CFRefCount &TF = static_cast<CFRefCount&>(C.getEngine().getTF()); + + const ProgramState *state = C.getState(); + + // Evaluate the effect of the arguments. + RefVal::Kind hasErr = (RefVal::Kind) 0; + SourceRange ErrorRange; + SymbolRef ErrorSym = 0; + + for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { + SVal V = CallOrMsg.getArgSValAsScalarOrLoc(idx); + + if (SymbolRef Sym = V.getAsLocSymbol()) { + if (RefBindings::data_type *T = state->get<RefBindings>(Sym)) { + state = TF.Update(state, Sym, *T, Summ.getArg(idx), hasErr); + if (hasErr) { + ErrorRange = CallOrMsg.getArgSourceRange(idx); + ErrorSym = Sym; + break; + } + } + } + } + + // Evaluate the effect on the message receiver. + bool ReceiverIsTracked = false; + if (!hasErr && Receiver) { + if (SymbolRef Sym = Receiver.getSValAsScalarOrLoc(state).getAsLocSymbol()) { + if (const RefVal *T = state->get<RefBindings>(Sym)) { + ReceiverIsTracked = true; + state = TF.Update(state, Sym, *T, Summ.getReceiverEffect(), hasErr); + if (hasErr) { + ErrorRange = Receiver.getSourceRange(); + ErrorSym = Sym; + } + } + } + } + + // Process any errors. + if (hasErr) { + processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C); + return; + } + + // Consult the summary for the return value. + RetEffect RE = Summ.getRetEffect(); + + if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) { + if (ReceiverIsTracked) + RE = TF.Summaries.getObjAllocRetEffect(); + else + RE = RetEffect::MakeNoRet(); + } + + switch (RE.getKind()) { + default: + llvm_unreachable("Unhandled RetEffect."); break; + + case RetEffect::NoRet: + // No work necessary. + break; + + case RetEffect::OwnedAllocatedSymbol: + case RetEffect::OwnedSymbol: { + SymbolRef Sym = state->getSVal(CallOrMsg.getOriginExpr()).getAsSymbol(); + if (!Sym) + break; + + // Use the result type from callOrMsg as it automatically adjusts + // for methods/functions that return references. + QualType ResultTy = CallOrMsg.getResultType(C.getASTContext()); + state = state->set<RefBindings>(Sym, RefVal::makeOwned(RE.getObjKind(), + ResultTy)); + + // FIXME: Add a flag to the checker where allocations are assumed to + // *not* fail. (The code below is out-of-date, though.) +#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; + } + + case RetEffect::GCNotOwnedSymbol: + case RetEffect::ARCNotOwnedSymbol: + case RetEffect::NotOwnedSymbol: { + const Expr *Ex = CallOrMsg.getOriginExpr(); + SymbolRef Sym = state->getSVal(Ex).getAsSymbol(); + if (!Sym) + break; + + // Use GetReturnType in order to give [NSFoo alloc] the type NSFoo *. + QualType ResultTy = GetReturnType(Ex, C.getASTContext()); + state = state->set<RefBindings>(Sym, RefVal::makeNotOwned(RE.getObjKind(), + ResultTy)); + break; + } + } + + // This check is actually necessary; otherwise the statement builder thinks + // we've hit a previously-found path. + // Normally addTransition takes care of this, but we want the node pointer. + ExplodedNode *NewNode; + if (state == C.getState()) { + NewNode = C.getPredecessor(); + } else { + NewNode = C.generateNode(state); + } + + // Annotate the edge with summary we used. + // FIXME: The summary log should live on RetainReleaseChecker. + if (NewNode) TF.SummaryLog[NewNode] = &Summ; +} + +void RetainReleaseChecker::processNonLeakError(const ProgramState *St, + SourceRange ErrorRange, + RefVal::Kind ErrorKind, + SymbolRef Sym, + CheckerContext &C) const { + ExplodedNode *N = C.generateSink(St); + if (!N) + return; + + // FIXME: This goes away once the these bug types move to the checker, + // and CFRefReport no longer depends on CFRefCount. + CFRefCount &TF = static_cast<CFRefCount&>(C.getEngine().getTF()); + + CFRefBug *BT; + switch (ErrorKind) { + default: + llvm_unreachable("Unhandled error."); + return; + case RefVal::ErrorUseAfterRelease: + BT = static_cast<CFRefBug*>(TF.useAfterRelease); + break; + case RefVal::ErrorReleaseNotOwned: + BT = static_cast<CFRefBug*>(TF.releaseNotOwned); + break; + case RefVal::ErrorDeallocGC: + BT = static_cast<CFRefBug*>(TF.deallocGC); + break; + case RefVal::ErrorDeallocNotOwned: + BT = static_cast<CFRefBug*>(TF.deallocNotOwned); + break; + } + + CFRefReport *report = new CFRefReport(*BT, TF, N, Sym); + report->addRange(ErrorRange); + C.EmitReport(report); +} + bool RetainReleaseChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // Get the callee. We're only interested in simple C functions. @@ -3635,19 +3710,25 @@ bool RetainReleaseChecker::evalCall(const CallExpr *CE, } state = state->BindExpr(CE, RetVal, false); - // FIXME: This will improve when RetainSummariesManager moves to the checker. - // Really we only want to handle ArgEffects and RetEffects; the arguments to - // CFRetain and CFMakeCollectable don't need to be invalidated. - // All of this can go away once the effects are handled in a post-call check. - CFRefCount &TF = static_cast<CFRefCount&>(C.getEngine().getTF()); - RetainSummary *Summ = TF.Summaries.getSummary(FD); - assert(Summ); + // FIXME: This should not be necessary, but otherwise the argument seems to be + // considered alive during the next statement. + if (const MemRegion *ArgRegion = RetVal.getAsRegion()) { + // Save the refcount status of the argument. + SymbolRef Sym = RetVal.getAsLocSymbol(); + RefBindings::data_type *Binding = 0; + if (Sym) + Binding = state->get<RefBindings>(Sym); - TF.evalSummary(C.getNodeSet(), C.getEngine(), C.getNodeBuilder(), CE, - CallOrObjCMessage(CE, C.getState()), - InstanceReceiver(), *Summ, L.getAsRegion(), - C.getPredecessor(), state); + // Invalidate the argument region. + unsigned Count = C.getNodeBuilder().getCurrentBlockCount(); + state = state->invalidateRegion(ArgRegion, CE, Count); + // Restore the refcount status of the argument. + if (Binding) + state = state->set<RefBindings>(Sym, *Binding); + } + + C.addTransition(state); return true; } |