Newer
Older
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, SymbolRef sym,
Ted Kremenek
committed
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())
Ted Kremenek
committed
return "[naming convention] leak of returned object (GC)";
Ted Kremenek
committed
if (getTF().getLangOptions().getGCMode() == LangOptions::HybridGC)
Ted Kremenek
committed
return "[naming convention] leak of returned object (hybrid MM, "
"non-GC)";
Ted Kremenek
committed
assert (getTF().getLangOptions().getGCMode() == LangOptions::NonGC);
Ted Kremenek
committed
return "[naming convention] leak of returned object";
Ted Kremenek
committed
}
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 {
Ted Kremenek
committed
SymbolRef Sym;
public:
Ted Kremenek
committed
CFRefReport(CFRefBug& D, ExplodedNode<GRState> *n, SymbolRef 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;
Ted Kremenek
committed
SymbolRef getSymbol() const { return Sym; }
PathDiagnosticPiece* getEndPath(BugReporter& BR,
const ExplodedNode<GRState>* N);
Ted Kremenek
committed
std::pair<const char**,const char**> getExtraDescriptiveText();
PathDiagnosticPiece* VisitNode(const ExplodedNode<GRState>* N,
const ExplodedNode<GRState>* PrevN,
const 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(const ExplodedNode<GRState>* N,
const ExplodedNode<GRState>* PrevN,
const ExplodedGraph<GRState>& G,
BugReporter& BR) {
// Check if the type state has changed.
GRStateManager &StMgr = cast<GRBugReporter>(BR).getStateManager();
GRStateRef PrevSt(PrevN->getState(), StMgr);
GRStateRef CurrSt(N->getState(), StMgr);
Ted Kremenek
committed
const RefVal* CurrT = CurrSt.get<RefBindings>(Sym);
if (!CurrT) return NULL;
const RefVal& CurrV = *CurrT;
const RefVal* PrevT = PrevSt.get<RefBindings>(Sym);
Ted Kremenek
committed
std::string sbuf;
llvm::raw_string_ostream os(sbuf);
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.GetSVal(CE->getCallee());
if (loc::FuncVal* FV = dyn_cast<loc::FuncVal>(&X))
os << "Call to function '" << FV->getDecl()->getNameAsString() <<'\'';
else
os << "function call";
os << " returns an object with a ";
}
else {
assert (isa<ObjCMessageExpr>(S));
os << "Method returns an object with a ";
if (CurrV.isOwned())
os << "+1 retain count (owning reference).";
else {
assert (CurrV.isNotOwned());
os << "+0 retain count (non-owning reference).";
FullSourceLoc Pos(S->getLocStart(), BR.getContext().getSourceManager());
Ted Kremenek
committed
PathDiagnosticPiece* P = new PathDiagnosticPiece(Pos, os.str());
if (Expr* Exp = dyn_cast<Expr>(S))
P->addRange(Exp->getSourceRange());
return P;
}
Ted Kremenek
committed
// Determine if the typestate has changed.
RefVal PrevV = *PrevT;
if (PrevV == CurrV)
return NULL;
// The typestate has changed.
Ted Kremenek
committed
std::string sbuf;
llvm::raw_string_ostream os(sbuf);
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()) {
break;
case RefVal::Released:
Ted Kremenek
committed
os << "Object released.";
break;
case RefVal::ReturnedOwned:
Ted Kremenek
committed
os << "Object returned to caller as an owning reference (single retain "
Ted Kremenek
committed
"count transferred to caller).";
break;
case RefVal::ReturnedNotOwned:
Ted Kremenek
committed
os << "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());
Ted Kremenek
committed
PathDiagnosticPiece* P = new PathDiagnosticPiece(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)) {
Ted Kremenek
committed
SVal X = CurrSt.GetSVal(Exp);
Zhongxing Xu
committed
if (loc::SymbolVal* SV = dyn_cast<loc::SymbolVal>(&X))
Ted Kremenek
committed
if (SV->getSymbol() == Sym) P->addRange(Exp->getSourceRange()); break;
}
Ted Kremenek
committed
namespace {
class VISIBILITY_HIDDEN FindUniqueBinding :
public StoreManager::BindingsHandler {
Ted Kremenek
committed
SymbolRef Sym;
Ted Kremenek
committed
MemRegion* Binding;
bool First;
public:
Ted Kremenek
committed
FindUniqueBinding(SymbolRef sym) : Sym(sym), Binding(0), First(true) {}
Ted Kremenek
committed
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
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
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<const ExplodedNode<GRState>*,const MemRegion*>
GetAllocationSite(GRStateManager* StateMgr, const ExplodedNode<GRState>* N,
Ted Kremenek
committed
SymbolRef 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.
const ExplodedNode<GRState>* Last = N;
const 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);
PathDiagnosticPiece*
CFRefReport::getEndPath(BugReporter& br, const ExplodedNode<GRState>* EndN) {
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.
const ExplodedNode<GRState>* AllocNode = 0;
const 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
SourceManager& SMgr = BR.getContext().getSourceManager();
unsigned AllocLine =SMgr.getInstantiationLineNumber(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.getInstantiationLineNumber(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.
const 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.getInstantiationLineNumber(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) {
// 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.
Ted Kremenek
committed
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"
Ted Kremenek
committed
" 'new' or 'alloc'. This violates the naming convention rules given"
Ted Kremenek
committed
" in the Memory Management Guide for Cocoa (object leaked).";
}
else
Ted Kremenek
committed
os << " is no longer referenced after this point and has a retain count of"
" +"
Ted Kremenek
committed
<< 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<SymbolRef, 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.
Ted Kremenek
committed
SymbolRef Sym = static_cast<CFRefReport&>(R).getSymbol();
const ExplodedNode<GRState>* AllocNode =
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);