Newer
Older
if (!T)
return;
Ted Kremenek
committed
// Change the reference count.
Ted Kremenek
committed
RefVal X = *T;
Ted Kremenek
committed
switch (X.getKind()) {
case RefVal::Owned: {
unsigned cnt = X.getCount();
Ted Kremenek
committed
assert (cnt > 0);
X = RefVal::makeReturnedOwned(cnt - 1);
break;
}
case RefVal::NotOwned: {
unsigned cnt = X.getCount();
X = cnt ? RefVal::makeReturnedOwned(cnt - 1)
: RefVal::makeReturnedNotOwned();
break;
}
default:
return;
}
// Update the binding.
Ted Kremenek
committed
state = state.set<RefBindings>(Sym, X);
Ted Kremenek
committed
Builder.MakeNode(Dst, S, Pred, state);
}
// Assumptions.
const GRState* CFRefCount::EvalAssume(GRStateManager& VMgr,
const GRState* St,
Zhongxing Xu
committed
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.
Ted Kremenek
committed
RefBindings B = St->get<RefBindings>();
if (B.isEmpty())
return St;
bool changed = false;
Ted Kremenek
committed
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());
}
}
Ted Kremenek
committed
if (changed)
state = state.set<RefBindings>(B);
Ted Kremenek
committed
return state;
}
Ted Kremenek
committed
RefBindings CFRefCount::Update(RefBindings B, SymbolID sym,
RefVal V, ArgEffect E,
Ted Kremenek
committed
RefVal::Kind& hasErr,
RefBindings::Factory& RefBFactory) {
// FIXME: This dispatch can potentially be sped up by unifiying it into
// a single switch statement. Opt for simplicity for now.
switch (E) {
default:
assert (false && "Unhandled CFRef transition.");
Ted Kremenek
committed
case MayEscape:
if (V.getKind() == RefVal::Owned) {
Ted Kremenek
committed
break;
}
// Fall-through.
case DoNothing:
if (!isGCEnabled() && V.getKind() == RefVal::Released) {
hasErr = V.getKind();
Ted Kremenek
committed
}
return B;
Ted Kremenek
committed
Ted Kremenek
committed
case Autorelease:
case StopTracking:
return RefBFactory.Remove(B, sym);
Ted Kremenek
committed
case IncRef:
switch (V.getKind()) {
default:
assert(false);
case RefVal::Owned:
case RefVal::NotOwned:
Ted Kremenek
committed
break;
case RefVal::Released:
if (isGCEnabled())
break;
Ted Kremenek
committed
}
Ted Kremenek
committed
break;
Ted Kremenek
committed
// Fall-through.
case DecRef:
switch (V.getKind()) {
default:
assert (false);
Ted Kremenek
committed
case RefVal::Owned:
V = V.getCount() > 1 ? V - 1 : V ^ RefVal::Released;
break;
case RefVal::NotOwned:
if (V.getCount() > 0)
V = V - 1;
hasErr = V.getKind();
Ted Kremenek
committed
}
break;
case RefVal::Released:
hasErr = V.getKind();
break;
Ted Kremenek
committed
}
Ted Kremenek
committed
break;
}
return RefBFactory.Add(B, sym, V);
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Error reporting.
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
namespace {
//===-------------===//
// Bug Descriptions. //
//===-------------===//
class VISIBILITY_HIDDEN CFRefBug : public BugTypeCacheLocation {
protected:
CFRefCount& TF;
public:
CFRefBug(CFRefCount& tf) : TF(tf) {}
CFRefCount& getTF() { return TF; }
const CFRefCount& getTF() const { return TF; }
Ted Kremenek
committed
virtual bool isLeak() const { return false; }
const char* getCategory() const {
return "Memory (Core Foundation/Objective-C)";
};
class VISIBILITY_HIDDEN UseAfterRelease : public CFRefBug {
public:
UseAfterRelease(CFRefCount& tf) : CFRefBug(tf) {}
virtual const char* getName() const {
return "use-after-release";
}
virtual const char* getDescription() const {
Ted Kremenek
committed
return "Reference-counted object is used after it is released.";
}
virtual void EmitWarnings(BugReporter& BR);
};
class VISIBILITY_HIDDEN BadRelease : public CFRefBug {
public:
BadRelease(CFRefCount& tf) : CFRefBug(tf) {}
virtual const char* getName() const {
return "bad release";
}
virtual const char* getDescription() const {
return "Incorrect decrement of the reference count of a "
"The object is not owned at this point by the caller.";
}
virtual void EmitWarnings(BugReporter& BR);
};
class VISIBILITY_HIDDEN Leak : public CFRefBug {
Ted Kremenek
committed
bool isReturn;
public:
Leak(CFRefCount& tf) : CFRefBug(tf) {}
Ted Kremenek
committed
void setIsReturn(bool x) { isReturn = x; }
virtual const char* getName() const {
Ted Kremenek
committed
if (!isReturn) {
if (getTF().isGCEnabled())
return "leak (GC)";
if (getTF().getLangOptions().getGCMode() == LangOptions::HybridGC)
return "leak (hybrid MM, non-GC)";
assert (getTF().getLangOptions().getGCMode() == LangOptions::NonGC);
return "leak";
}
else {
if (getTF().isGCEnabled())
return "leak of returned object (GC)";
if (getTF().getLangOptions().getGCMode() == LangOptions::HybridGC)
return "leak of returned object (hybrid MM, non-GC)";
assert (getTF().getLangOptions().getGCMode() == LangOptions::NonGC);
return "leak of returned object";
}
}
virtual const char* getDescription() const {
Ted Kremenek
committed
return "Object leaked";
}
virtual void EmitWarnings(BugReporter& BR);
virtual void GetErrorNodes(std::vector<ExplodedNode<GRState>*>& Nodes);
Ted Kremenek
committed
virtual bool isLeak() const { return true; }
virtual bool isCached(BugReport& R);
};
//===---------===//
// Bug Reports. //
//===---------===//
class VISIBILITY_HIDDEN CFRefReport : public RangedBugReport {
SymbolID Sym;
public:
CFRefReport(CFRefBug& D, ExplodedNode<GRState> *n, SymbolID sym)
: RangedBugReport(D, n), Sym(sym) {}
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);
Ted Kremenek
committed
else
beg = end = 0;
SymbolID getSymbol() const { return Sym; }
Ted Kremenek
committed
virtual PathDiagnosticPiece* getEndPath(BugReporter& BR,
Ted Kremenek
committed
virtual std::pair<const char**,const char**> getExtraDescriptiveText();
virtual PathDiagnosticPiece* VisitNode(ExplodedNode<GRState>* N,
ExplodedNode<GRState>* PrevN,
ExplodedGraph<GRState>& G,
BugReporter& BR);
};
} // end anonymous namespace
void CFRefCount::RegisterChecks(GRExprEngine& Eng) {
Eng.Register(new UseAfterRelease(*this));
Eng.Register(new BadRelease(*this));
Eng.Register(new Leak(*this));
}
static const char* Msgs[] = {
"Code is compiled in garbage collection only mode" // GC only
" (the bug occurs with garbage collection enabled).",
"Code is compiled without garbage collection.", // No GC.
"Code is compiled for use with and without garbage collection (GC)."
" The bug occurs with GC enabled.", // Hybrid, with GC.
"Code is compiled for use with and without garbage collection (GC)."
" The bug occurs in non-GC mode." // Hyrbird, without GC/
};
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());
Ted Kremenek
committed
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);
}
}
PathDiagnosticPiece* CFRefReport::VisitNode(ExplodedNode<GRState>* N,
ExplodedNode<GRState>* PrevN,
ExplodedGraph<GRState>& G,
BugReporter& BR) {
// Check if the type state has changed.
const GRState* PrevSt = PrevN->getState();
const GRState* CurrSt = N->getState();
Ted Kremenek
committed
RefBindings PrevB = PrevSt->get<RefBindings>();
RefBindings CurrB = CurrSt->get<RefBindings>();
Ted Kremenek
committed
const RefVal* PrevT = PrevB.lookup(Sym);
const RefVal* CurrT = CurrB.lookup(Sym);
if (!CurrT)
return NULL;
Ted Kremenek
committed
const RefVal& CurrV = *CurrB.lookup(Sym);
Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
if (CurrV.isOwned()) {
if (isa<CallExpr>(S))
Msg = "Function call returns an object with a +1 retain count"
" (owning reference).";
else {
assert (isa<ObjCMessageExpr>(S));
Msg = "Method returns an object with a +1 retain count"
" (owning reference).";
}
}
else {
assert (CurrV.isNotOwned());
if (isa<CallExpr>(S))
Msg = "Function call returns an object with a +0 retain count"
" (non-owning reference).";
else {
assert (isa<ObjCMessageExpr>(S));
Msg = "Method returns an object with a +0 retain count"
" (non-owning reference).";
}
FullSourceLoc Pos(S->getLocStart(), BR.getContext().getSourceManager());
PathDiagnosticPiece* P = new PathDiagnosticPiece(Pos, Msg);
if (Expr* Exp = dyn_cast<Expr>(S))
P->addRange(Exp->getSourceRange());
return P;
}
Ted Kremenek
committed
// Determine if the typestate has changed.
RefVal PrevV = *PrevB.lookup(Sym);
if (PrevV == CurrV)
return NULL;
// The typestate has changed.
std::ostringstream os;
Ted Kremenek
committed
std::string s;
switch (CurrV.getKind()) {
case RefVal::Owned:
case RefVal::NotOwned:
Ted Kremenek
committed
if (PrevV.getCount() == CurrV.getCount())
return 0;
if (PrevV.getCount() > CurrV.getCount())
os << "Reference count decremented.";
else
os << "Reference count incremented.";
Ted Kremenek
committed
if (unsigned Count = CurrV.getCount()) {
Ted Kremenek
committed
s = os.str();
Msg = s.c_str();
break;
case RefVal::Released:
Msg = "Object released.";
break;
case RefVal::ReturnedOwned:
Ted Kremenek
committed
Msg = "Object returned to caller as an owning reference (single retain "
"count transferred to caller).";
break;
case RefVal::ReturnedNotOwned:
Msg = "Object returned to caller with a +0 (non-owning) retain count.";
break;
default:
return NULL;
}
Stmt* S = cast<PostStmt>(N->getLocation()).getStmt();
FullSourceLoc Pos(S->getLocStart(), BR.getContext().getSourceManager());
PathDiagnosticPiece* P = new PathDiagnosticPiece(Pos, Msg);
// Add the range by scanning the children of the statement for any bindings
// to Sym.
GRStateManager& VSM = cast<GRBugReporter>(BR).getStateManager();
for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
if (Expr* Exp = dyn_cast_or_null<Expr>(*I)) {
Zhongxing Xu
committed
SVal X = VSM.GetSVal(CurrSt, Exp);
Zhongxing Xu
committed
if (loc::SymbolVal* SV = dyn_cast<loc::SymbolVal>(&X))
if (SV->getSymbol() == Sym) {
P->addRange(Exp->getSourceRange()); break;
}
}
return P;
}
Ted Kremenek
committed
namespace {
class VISIBILITY_HIDDEN FindUniqueBinding :
public StoreManager::BindingsHandler {
SymbolID Sym;
MemRegion* Binding;
bool First;
public:
FindUniqueBinding(SymbolID sym) : Sym(sym), Binding(0), First(true) {}
Zhongxing Xu
committed
bool HandleBinding(StoreManager& SMgr, Store store, MemRegion* R, SVal val) {
if (const loc::SymbolVal* SV = dyn_cast<loc::SymbolVal>(&val)) {
Ted Kremenek
committed
if (SV->getSymbol() != Sym)
return true;
}
Zhongxing Xu
committed
else if (const nonloc::SymbolVal* SV=dyn_cast<nonloc::SymbolVal>(&val)) {
Ted Kremenek
committed
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
if (SV->getSymbol() != Sym)
return true;
}
else
return true;
if (Binding) {
First = false;
return false;
}
else
Binding = R;
return true;
}
operator bool() { return First && Binding; }
MemRegion* getRegion() { return Binding; }
};
}
static std::pair<ExplodedNode<GRState>*,MemRegion*>
Ted Kremenek
committed
GetAllocationSite(GRStateManager* StateMgr, ExplodedNode<GRState>* N,
SymbolID Sym) {
Ted Kremenek
committed
Ted Kremenek
committed
// Find both first node that referred to the tracked symbol and the
// memory location that value was store to.
Ted Kremenek
committed
MemRegion* FirstBinding = 0;
Ted Kremenek
committed
while (N) {
Ted Kremenek
committed
RefBindings B = St->get<RefBindings>();
Ted Kremenek
committed
if (!B.lookup(Sym))
Ted Kremenek
committed
break;
Ted Kremenek
committed
if (StateMgr) {
Ted Kremenek
committed
FindUniqueBinding FB(Sym);
StateMgr->iterBindings(St, FB);
if (FB) FirstBinding = FB.getRegion();
}
Ted Kremenek
committed
Last = N;
N = N->pred_empty() ? NULL : *(N->pred_begin());
}
Ted Kremenek
committed
return std::make_pair(Last, FirstBinding);
Ted Kremenek
committed
PathDiagnosticPiece* CFRefReport::getEndPath(BugReporter& br,
Ted Kremenek
committed
Ted Kremenek
committed
GRBugReporter& BR = cast<GRBugReporter>(br);
Ted Kremenek
committed
// Tell the BugReporter to report cases when the tracked symbol is
// assigned to different variables, etc.
cast<GRBugReporter>(BR).addNotableSymbol(Sym);
Ted Kremenek
committed
if (!getBugType().isLeak())
return RangedBugReport::getEndPath(BR, EndN);
Ted Kremenek
committed
// We are 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.
Ted Kremenek
committed
MemRegion* FirstBinding = 0;
Ted Kremenek
committed
llvm::tie(AllocNode, FirstBinding) =
GetAllocationSite(&BR.getStateManager(), EndN, Sym);
// Get the allocate site.
assert (AllocNode);
Stmt* FirstStmt = cast<PostStmt>(AllocNode->getLocation()).getStmt();
Ted Kremenek
committed
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
SourceManager& SMgr = BR.getContext().getSourceManager();
unsigned AllocLine = SMgr.getLogicalLineNumber(FirstStmt->getLocStart());
// Get the leak site. We may have multiple ExplodedNodes (one with the
// leak) that occur on the same line number; if the node with the leak
// has any immediate predecessor nodes with the same line number, find
// any transitive-successors that have a different statement and use that
// line number instead. This avoids emiting a diagnostic like:
//
// // 'y' is leaked.
// int x = foo(y);
//
// instead we want:
//
// int x = foo(y);
// // 'y' is leaked.
Stmt* S = getStmt(BR); // This is the statement where the leak occured.
assert (S);
unsigned EndLine = SMgr.getLogicalLineNumber(S->getLocStart());
// Look in the *trimmed* graph at the immediate predecessor of EndN. Does
// it occur on the same line?
PathDiagnosticPiece::DisplayHint Hint = PathDiagnosticPiece::Above;
assert (!EndN->pred_empty()); // Not possible to have 0 predecessors.
ExplodedNode<GRState> *Pred = *(EndN->pred_begin());
ProgramPoint PredPos = Pred->getLocation();
if (PostStmt* PredPS = dyn_cast<PostStmt>(&PredPos)) {
Stmt* SPred = PredPS->getStmt();
// Predecessor at same line?
if (SMgr.getLogicalLineNumber(SPred->getLocStart()) != EndLine) {
Hint = PathDiagnosticPiece::Below;
S = SPred;
}
}
// Generate the diagnostic.
FullSourceLoc L( S->getLocStart(), SMgr);
Ted Kremenek
committed
std::ostringstream os;
os << "Object allocated on line " << AllocLine;
Ted Kremenek
committed
if (FirstBinding)
Ted Kremenek
committed
os << " and stored into '" << FirstBinding->getString() << '\'';
Ted Kremenek
committed
// Get the retain count.
const RefVal* RV = EndN->getState()->get<RefBindings>(Sym);
Ted Kremenek
committed
Ted Kremenek
committed
if (RV->getKind() == RefVal::ErrorLeakReturned) {
ObjCMethodDecl& MD = cast<ObjCMethodDecl>(BR.getGraph().getCodeDecl());
os << " is returned from a method whose name ('"
<< MD.getSelector().getName()
<< "') does not contain 'create', "
"'copy', or 'new'. 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).";
Ted Kremenek
committed
return new PathDiagnosticPiece(L, os.str(), Hint);
Ted Kremenek
committed
}
void UseAfterRelease::EmitWarnings(BugReporter& BR) {
Ted Kremenek
committed
for (CFRefCount::use_after_iterator I = TF.use_after_begin(),
E = TF.use_after_end(); I != E; ++I) {
CFRefReport report(*this, I->first, I->second.second);
report.addRange(I->second.first->getSourceRange());
BR.EmitWarning(report);
Ted Kremenek
committed
}
}
void BadRelease::EmitWarnings(BugReporter& BR) {
Ted Kremenek
committed
for (CFRefCount::bad_release_iterator I = TF.bad_release_begin(),
E = TF.bad_release_end(); I != E; ++I) {
CFRefReport report(*this, I->first, I->second.second);
report.addRange(I->second.first->getSourceRange());
BR.EmitWarning(report);
}
}
Ted Kremenek
committed
void Leak::EmitWarnings(BugReporter& BR) {
for (CFRefCount::leaks_iterator I = TF.leaks_begin(),
E = TF.leaks_end(); I != E; ++I) {
Ted Kremenek
committed
std::vector<std::pair<SymbolID, bool> >& SymV = *(I->second);
unsigned n = SymV.size();
for (unsigned i = 0; i < n; ++i) {
Ted Kremenek
committed
setIsReturn(SymV[i].second);
CFRefReport report(*this, I->first, SymV[i].first);
BR.EmitWarning(report);
}
}
}
void Leak::GetErrorNodes(std::vector<ExplodedNode<GRState>*>& Nodes) {
for (CFRefCount::leaks_iterator I=TF.leaks_begin(), E=TF.leaks_end();
I!=E; ++I)
Nodes.push_back(I->first);
}
bool Leak::isCached(BugReport& R) {
// Most bug reports are cached at the location where they occured.
// With leaks, we want to unique them by the location where they were
Ted Kremenek
committed
// allocated, and only report a single path.
SymbolID Sym = static_cast<CFRefReport&>(R).getSymbol();
Ted Kremenek
committed
GetAllocationSite(0, R.getEndNode(), Sym).first;
if (!AllocNode)
return false;
return BugTypeCacheLocation::isCached(AllocNode->getLocation());
}
//===----------------------------------------------------------------------===//
// Transfer function creation for external clients.
//===----------------------------------------------------------------------===//
GRTransferFuncs* clang::MakeCFRefCountTF(ASTContext& Ctx, bool GCEnabled,
const LangOptions& lopts) {
Ted Kremenek
committed
return new CFRefCount(Ctx, GCEnabled, lopts);