retain/release checker: Flag a warning for non-owned objects returned

where an owned one is expected.  Also add preliminary checking for
returning a positive retain count object in GC mode where an owned GC
object is expected.

llvm-svn: 71388
This commit is contained in:
Ted Kremenek 2009-05-10 06:25:57 +00:00
parent 9dc1bed4a6
commit dee56e37fc
3 changed files with 112 additions and 13 deletions

View File

@ -296,7 +296,7 @@ public:
bool isOwned() const {
return K == OwnedSymbol || K == OwnedAllocatedSymbol;
}
static RetEffect MakeAlias(unsigned Idx) {
return RetEffect(Alias, Idx);
}
@ -1429,7 +1429,9 @@ public:
ErrorLeak, // A memory leak due to excessive reference counts.
ErrorLeakReturned, // A memory leak due to the returning method not having
// the correct naming conventions.
ErrorOverAutorelease
ErrorGCLeakReturned,
ErrorOverAutorelease,
ErrorReturnedNotOwned
};
private:
@ -1586,6 +1588,10 @@ void RefVal::print(std::ostream& Out) const {
Out << "Leaked (Bad naming)";
break;
case ErrorGCLeakReturned:
Out << "Leaked (GC-ed at return)";
break;
case ErrorUseAfterRelease:
Out << "Use-After-Release [ERROR]";
break;
@ -1597,6 +1603,10 @@ void RefVal::print(std::ostream& Out) const {
case RefVal::ErrorOverAutorelease:
Out << "Over autoreleased";
break;
case RefVal::ErrorReturnedNotOwned:
Out << "Non-owned object returned instead of owned";
break;
}
if (ACnt) {
@ -1695,6 +1705,7 @@ private:
BugType *deallocGC, *deallocNotOwned;
BugType *leakWithinFunction, *leakAtReturn;
BugType *overAutorelease;
BugType *returnNotOwnedForOwned;
BugReporter *BR;
GRStateRef Update(GRStateRef state, SymbolRef sym, RefVal V, ArgEffect E,
@ -1721,7 +1732,8 @@ public:
: Summaries(Ctx, gcenabled),
LOpts(lopts), useAfterRelease(0), releaseNotOwned(0),
deallocGC(0), deallocNotOwned(0),
leakWithinFunction(0), leakAtReturn(0), overAutorelease(0), BR(0) {}
leakWithinFunction(0), leakAtReturn(0), overAutorelease(0),
returnNotOwnedForOwned(0), BR(0) {}
virtual ~CFRefCount() {}
@ -1925,6 +1937,17 @@ namespace {
}
};
class VISIBILITY_HIDDEN ReturnedNotOwnedForOwned : public CFRefBug {
public:
ReturnedNotOwnedForOwned(CFRefCount *tf) :
CFRefBug(tf, "Method should return an owned object") {}
const char *getDescription() const {
return "Object with +0 retain counts returned to caller where a +1 "
"(owning) retain count is expected";
}
};
class VISIBILITY_HIDDEN Leak : public CFRefBug {
const bool isReturn;
protected:
@ -2027,6 +2050,9 @@ void CFRefCount::RegisterChecks(BugReporter& BR) {
overAutorelease = new OverAutorelease(this);
BR.Register(overAutorelease);
returnNotOwnedForOwned = new ReturnedNotOwnedForOwned(this);
BR.Register(returnNotOwnedForOwned);
// First register "return" leaks.
const char* name = 0;
@ -2488,6 +2514,13 @@ CFRefLeakReport::getEndPath(BugReporterContext& BRC,
" 'new' or 'alloc'. This violates the naming convention rules given"
" in the Memory Management Guide for Cocoa (object leaked)";
}
else if (RV->getKind() == RefVal::ErrorGCLeakReturned) {
ObjCMethodDecl& MD = cast<ObjCMethodDecl>(BRC.getCodeDecl());
os << " and returned from method '" << MD.getSelector().getAsString()
<< "' is potentially leaked when using garbage collection. Callers"
" of this method do not expect a +1 retain count since the return"
" type is an Objective-C object reference";
}
else
os << " is no longer referenced after this point and has a retain count of"
" +" << RV->getCount() << " (object leaked)";
@ -3031,26 +3064,65 @@ void CFRefCount::EvalReturn(ExplodedNodeSet<GRState>& Dst,
// Any leaks or other errors?
if (X.isReturnedOwned() && X.getCount() == 0) {
const Decl *CD = &Eng.getStateManager().getCodeDecl();
const Decl *CD = &Eng.getStateManager().getCodeDecl();
if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(CD)) {
const RetainSummary &Summ = *Summaries.getMethodSummary(MD);
if (!Summ.getRetEffect().isOwned()) {
static int ReturnOwnLeakTag = 0;
state = state.set<RefBindings>(Sym, X ^ RefVal::ErrorLeakReturned);
RetEffect RE = Summ.getRetEffect();
bool hasError = false;
if (isGCEnabled() && RE.getObjKind() == RetEffect::ObjC) {
// Things are more complicated with garbage collection. If the
// returned object is suppose to be an Objective-C object, we have
// a leak (as the caller expects a GC'ed object).
X = X ^ RefVal::ErrorGCLeakReturned;
// Keep this false until this is properly tested.
hasError = false;
}
else if (!RE.isOwned()) {
// Either we are using GC and the returned object is a CF type
// or we aren't using GC. In either case, we expect that the
// enclosing method is expected to return ownership.
hasError = true;
X = X ^ RefVal::ErrorLeakReturned;
}
if (hasError) {
// Generate an error node.
if (ExplodedNode<GRState> *N =
Builder.generateNode(PostStmt(S, &ReturnOwnLeakTag), state, Pred)) {
CFRefLeakReport *report =
static int ReturnOwnLeakTag = 0;
state = state.set<RefBindings>(Sym, X);
ExplodedNode<GRState> *N =
Builder.generateNode(PostStmt(S, &ReturnOwnLeakTag), state, Pred);
if (N) {
CFRefReport *report =
new CFRefLeakReport(*static_cast<CFRefBug*>(leakAtReturn), *this,
N, Sym, Eng);
BR->EmitReport(report);
}
}
}
}
else if (X.isReturnedNotOwned()) {
const Decl *CD = &Eng.getStateManager().getCodeDecl();
if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(CD)) {
const RetainSummary &Summ = *Summaries.getMethodSummary(MD);
if (Summ.getRetEffect().isOwned()) {
// Trying to return a not owned object to a caller expecting an
// owned object.
static int ReturnNotOwnedForOwnedTag = 0;
state = state.set<RefBindings>(Sym, X ^ RefVal::ErrorReturnedNotOwned);
if (ExplodedNode<GRState> *N =
Builder.generateNode(PostStmt(S, &ReturnNotOwnedForOwnedTag),
state, Pred)) {
CFRefReport *report =
new CFRefReport(*static_cast<CFRefBug*>(returnNotOwnedForOwned),
*this, N, Sym);
BR->EmitReport(report);
}
}
}
}
}
// Assumptions.

View File

@ -124,6 +124,20 @@ void f3() {
CFRetain(A);
}
// Test return of non-owned objects in contexts where an owned object
// is expected.
@interface TestReturnNotOwnedWhenExpectedOwned
- (NSString*)newString;
@end
@implementation TestReturnNotOwnedWhenExpectedOwned
- (NSString*)newString {
NSString *s = [NSString stringWithUTF8String:"hello"];
// FIXME: Should this be an error anyway?
return s; // no-warning
}
@end
//===----------------------------------------------------------------------===//
// Tests of ownership attributes.
//===----------------------------------------------------------------------===//

View File

@ -341,6 +341,19 @@ void f15() {
}
@end
// Test return of non-owned objects in contexts where an owned object
// is expected.
@interface TestReturnNotOwnedWhenExpectedOwned
- (NSString*)newString;
@end
@implementation TestReturnNotOwnedWhenExpectedOwned
- (NSString*)newString {
NSString *s = [NSString stringWithUTF8String:"hello"];
return s; // expected-warning{{Object with +0 retain counts returned to caller where a +1 (owning) retain count is expected}}
}
@end
// <rdar://problem/6659160>
int isFoo(char c);