Newer
Older
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 {
public:
Leak(CFRefCount& tf) : CFRefBug(tf) {}
virtual const char* getName() const {
if (getTF().isGCEnabled())
if (getTF().getLangOptions().getGCMode() == LangOptions::HybridGC)
return "leak (hybrid MM, non-GC)";
assert (getTF().getLangOptions().getGCMode() == LangOptions::NonGC);
}
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:
Msg = "Object returned to caller as 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)) {
RVal X = VSM.GetRVal(CurrSt, Exp);
if (lval::SymbolVal* SV = dyn_cast<lval::SymbolVal>(&X))
if (SV->getSymbol() == Sym) {
P->addRange(Exp->getSourceRange()); break;
}
}
return P;
}
Ted Kremenek
committed
static std::pair<ExplodedNode<GRState>*,store::Binding>
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
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) {
llvm::SmallVector<store::Binding, 5> Bindings;
StateMgr->getBindings(Bindings, St, Sym);
if (Bindings.size() == 1)
FirstBinding = Bindings[0];
}
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);
// Get the retain count.
Ted Kremenek
committed
unsigned long RetCount = EndN->getState()->get<RefBindings>(Sym)->getCount();
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
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
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
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)
os << " and stored into '"
<< BR.getStateManager().BindingAsString(FirstBinding) << '\'';
os << " is no longer referenced after this point and has a retain count of +"
<< RetCount << " (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) {
std::vector<SymbolID>& SymV = *(I->second);
unsigned n = SymV.size();
for (unsigned i = 0; i < n; ++i) {
CFRefReport report(*this, I->first, SymV[i]);
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
// allocated, and only report only 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);