Newer
Older
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; }
};
class VISIBILITY_HIDDEN UseAfterRelease : public CFRefBug {
public:
UseAfterRelease(CFRefCount& tf) : CFRefBug(tf) {}
virtual const char* getName() const {
}
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 {
}
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())
return "Memory Leak (GC)";
if (getTF().getLangOptions().getGCMode() == LangOptions::HybridGC)
return "Memory Leak (Hybrid MM, non-GC)";
assert (getTF().getLangOptions().getGCMode() == LangOptions::NonGC);
return "Memory Leak";
}
virtual const char* getDescription() const {
Ted Kremenek
committed
return "Object leaked";
}
virtual void EmitWarnings(BugReporter& BR);
virtual void GetErrorNodes(std::vector<ExplodedNode<ValueState>*>& 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<ValueState> *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,
ExplodedNode<ValueState>* N);
virtual std::pair<const char**,const char**> getExtraDescriptiveText();
virtual PathDiagnosticPiece* VisitNode(ExplodedNode<ValueState>* N,
ExplodedNode<ValueState>* PrevN,
ExplodedGraph<ValueState>& 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<ValueState>* N,
ExplodedNode<ValueState>* PrevN,
ExplodedGraph<ValueState>& G,
BugReporter& BR) {
// Check if the type state has changed.
const ValueState* PrevSt = PrevN->getState();
const ValueState* CurrSt = N->getState();
CFRefCount::RefBindings PrevB = CFRefCount::GetRefBindings(*PrevSt);
CFRefCount::RefBindings CurrB = CFRefCount::GetRefBindings(*CurrSt);
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;
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()) {
Msg = os.str().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.
ValueStateManager& 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;
}
static std::pair<ExplodedNode<ValueState>*,VarDecl*>
GetAllocationSite(ExplodedNode<ValueState>* N, SymbolID Sym) {
Ted Kremenek
committed
Ted Kremenek
committed
ExplodedNode<ValueState>* Last = N;
// Find the first node that referred to the tracked symbol. We also
// try and find the first VarDecl the value was stored to.
VarDecl* FirstDecl = 0;
Ted Kremenek
committed
while (N) {
const ValueState* St = N->getState();
Ted Kremenek
committed
RefBindings B = RefBindings((RefBindings::TreeTy*) St->CheckerState);
Ted Kremenek
committed
if (!B.lookup(Sym))
Ted Kremenek
committed
break;
VarDecl* VD = 0;
// Determine if there is an LVal binding to the symbol.
for (ValueState::vb_iterator I=St->vb_begin(), E=St->vb_end(); I!=E; ++I) {
if (!isa<lval::SymbolVal>(I->second) // Is the value a symbol?
|| cast<lval::SymbolVal>(I->second).getSymbol() != Sym)
continue;
if (VD) { // Multiple decls map to this symbol.
VD = 0;
break;
}
VD = I->first;
}
if (VD) FirstDecl = VD;
Ted Kremenek
committed
Last = N;
N = N->pred_empty() ? NULL : *(N->pred_begin());
}
return std::make_pair(Last, FirstDecl);
}
PathDiagnosticPiece* CFRefReport::getEndPath(BugReporter& BR,
ExplodedNode<ValueState>* EndN) {
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);
typedef CFRefCount::RefBindings RefBindings;
// Get the retain count.
Ted Kremenek
committed
unsigned long RetCount =
CFRefCount::GetRefBindings(*EndN->getState()).lookup(Sym)->getCount();
// 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.
ExplodedNode<ValueState>* AllocNode = 0;
VarDecl* FirstDecl = 0;
llvm::tie(AllocNode, FirstDecl) = GetAllocationSite(EndN, Sym);
// Get the allocate site.
assert (AllocNode);
Stmt* FirstStmt = cast<PostStmt>(AllocNode->getLocation()).getStmt();
Ted Kremenek
committed
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
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<ValueState> *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;
if (FirstDecl)
os << " and stored into '" << FirstDecl->getName() << '\'';
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<ValueState>*>& 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();
ExplodedNode<ValueState>* AllocNode =
GetAllocationSite(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);