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