[analyzer] Warn on use of 'self' after call to to [super dealloc].

Referring to 'self' after a call to [super dealloc] is a use-after-free in
Objective-C because NSObject's -dealloc frees the memory pointed to by self.
This patch extends the ObjCSuperDeallocChecker to catch this error.

rdar://problem/6953275

Differential Revision: http://reviews.llvm.org/D17528

llvm-svn: 261935
This commit is contained in:
Devin Coughlin 2016-02-25 23:36:52 +00:00
parent 7bd3499d05
commit 591991c86f
2 changed files with 200 additions and 43 deletions

View File

@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
//
// This defines ObjCSuperDeallocChecker, a builtin check that warns when
// [super dealloc] is called twice on the same instance in MRR mode.
// self is used after a call to [super dealloc] in MRR mode.
//
//===----------------------------------------------------------------------===//
@ -25,7 +25,8 @@ using namespace ento;
namespace {
class ObjCSuperDeallocChecker
: public Checker<check::PostObjCMessage, check::PreObjCMessage> {
: public Checker<check::PostObjCMessage, check::PreObjCMessage,
check::PreCall, check::Location> {
mutable IdentifierInfo *IIdealloc, *IINSObject;
mutable Selector SELdealloc;
@ -40,12 +41,24 @@ public:
ObjCSuperDeallocChecker();
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkLocation(SVal l, bool isLoad, const Stmt *S,
CheckerContext &C) const;
private:
void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const;
void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S,
CheckerContext &C) const;
};
} // End anonymous namespace.
// Remember whether [super dealloc] has previously been called on the
// a SymbolRef for the receiver.
// SymbolRef for the receiver.
REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)
class SuperDeallocBRVisitor final
@ -71,40 +84,36 @@ public:
void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
CheckerContext &C) const {
if (!isSuperDeallocMessage(M))
return;
ProgramStateRef State = C.getState();
SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
if (!ReceiverSymbol) {
diagnoseCallArguments(M, C);
return;
}
bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
// If [super dealloc] has not been called, there is nothing to do. We'll
// note the fact that [super dealloc] was called in checkPostObjCMessage.
if (!AlreadyCalled)
return;
// We have a duplicate [super dealloc] method call.
// This likely causes a crash, so stop exploring the
// path by generating a sink.
ExplodedNode *ErrNode = C.generateErrorNode();
// If we've already reached this node on another path, return.
if (!ErrNode)
return;
StringRef Desc;
// Generate the report.
std::unique_ptr<BugReport> BR(
new BugReport(*DoubleSuperDeallocBugType,
"[super dealloc] should not be called multiple times",
ErrNode));
BR->addRange(M.getOriginExpr()->getSourceRange());
BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(ReceiverSymbol));
C.emitReport(std::move(BR));
if (isSuperDeallocMessage(M)) {
Desc = "[super dealloc] should not be called multiple times";
} else {
Desc = StringRef();
}
reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C);
return;
}
void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
diagnoseCallArguments(Call, C);
}
void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
CheckerContext &C) const {
// Check for [super dealloc] method call.
@ -122,6 +131,92 @@ void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
C.addTransition(State);
}
void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
CheckerContext &C) const {
SymbolRef BaseSym = L.getLocSymbolInBase();
if (!BaseSym)
return;
ProgramStateRef State = C.getState();
if (!State->contains<CalledSuperDealloc>(BaseSym))
return;
const MemRegion *R = L.getAsRegion();
if (!R)
return;
// Climb the super regions to find the base symbol while recording
// the second-to-last region for error reporting.
const MemRegion *PriorSubRegion = nullptr;
while (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) {
BaseSym = SymR->getSymbol();
break;
} else {
R = SR->getSuperRegion();
PriorSubRegion = SR;
}
}
StringRef Desc = StringRef();
auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion);
if (IvarRegion) {
std::string Buf;
llvm::raw_string_ostream OS(Buf);
OS << "use of instance variable '" << *IvarRegion->getDecl() <<
"' after the instance has been freed with call to [super dealloc]";
Desc = OS.str();
}
reportUseAfterDealloc(BaseSym, Desc, S, C);
}
/// Report a use-after-dealloc on \param Sym. If not empty,
/// \param Desc will be used to describe the error; otherwise,
/// a default warning will be used.
void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym,
StringRef Desc,
const Stmt *S,
CheckerContext &C) const {
// We have a use of self after free.
// This likely causes a crash, so stop exploring the
// path by generating a sink.
ExplodedNode *ErrNode = C.generateErrorNode();
// If we've already reached this node on another path, return.
if (!ErrNode)
return;
if (Desc.empty())
Desc = "use of 'self' after it has been freed with call to [super dealloc]";
// Generate the report.
std::unique_ptr<BugReport> BR(
new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode));
BR->addRange(S->getSourceRange());
BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(Sym));
C.emitReport(std::move(BR));
}
/// Diagnose if any of the arguments to \param CE have already been
/// dealloc'd.
void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
unsigned ArgCount = CE.getNumArgs();
for (unsigned I = 0; I < ArgCount; I++) {
SymbolRef Sym = CE.getArgSVal(I).getAsSymbol();
if (!Sym)
continue;
if (State->contains<CalledSuperDealloc>(Sym)) {
reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C);
return;
}
}
}
ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
: IIdealloc(nullptr), IINSObject(nullptr) {

View File

@ -35,8 +35,9 @@ typedef struct objc_selector *SEL;
return self;
}
- (void)dealloc {
[super dealloc];
[_ivar release];
[super dealloc]; // expected-note {{[super dealloc] called here}}
[_ivar release]; // expected-warning {{use of instance variable '_ivar' after the instance has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of instance variable '_ivar' after the instance has been freed with call to [super dealloc]}}
}
@end
@ -54,8 +55,46 @@ typedef struct objc_selector *SEL;
return self;
}
- (void)dealloc {
[super dealloc];
_delegate = nil;
[super dealloc]; // expected-note {{[super dealloc] called here}}
_delegate = nil; // expected-warning {{use of instance variable '_delegate' after the instance has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of instance variable '_delegate' after the instance has been freed with call to [super dealloc]}}
}
@end
struct SomeStruct {
int f;
};
@interface SuperDeallocThenAssignIvarField : NSObject {
struct SomeStruct _s;
}
@end
@implementation SuperDeallocThenAssignIvarField
- (void)dealloc {
[super dealloc]; // expected-note {{[super dealloc] called here}}
_s.f = 7; // expected-warning {{use of instance variable '_s' after the instance has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of instance variable '_s' after the instance has been freed with call to [super dealloc]}}
}
@end
@interface OtherClassWithIvar {
@public
int _otherIvar;
}
@end;
@interface SuperDeallocThenAssignIvarIvar : NSObject {
OtherClassWithIvar *_ivar;
}
@end
@implementation SuperDeallocThenAssignIvarIvar
- (void)dealloc {
[super dealloc]; // expected-note {{[super dealloc] called here}}
_ivar->_otherIvar = 7; // expected-warning {{use of instance variable '_ivar' after the instance has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of instance variable '_ivar' after the instance has been freed with call to [super dealloc]}}
}
@end
@ -72,8 +111,9 @@ typedef struct objc_selector *SEL;
return self;
}
- (void)dealloc {
[super dealloc];
self.ivar = nil;
[super dealloc]; // expected-note {{[super dealloc] called here}}
self.ivar = nil; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@ -90,8 +130,9 @@ typedef struct objc_selector *SEL;
return self;
}
- (void)dealloc {
[super dealloc];
self.delegate = nil;
[super dealloc]; // expected-note {{[super dealloc] called here}}
self.delegate = nil; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@ -103,8 +144,9 @@ typedef struct objc_selector *SEL;
- (void)_invalidate {
}
- (void)dealloc {
[super dealloc];
[self _invalidate];
[super dealloc]; // expected-note {{[super dealloc] called here}}
[self _invalidate]; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@ -117,8 +159,23 @@ static void _invalidate(NSObject *object) {
@implementation SuperDeallocThenCallNonObjectiveCMethodClass
- (void)dealloc {
[super dealloc];
_invalidate(self);
[super dealloc]; // expected-note {{[super dealloc] called here}}
_invalidate(self); // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@interface SuperDeallocThenCallObjectiveClassMethodClass : NSObject { }
@end
@implementation SuperDeallocThenCallObjectiveClassMethodClass
+ (void) invalidate:(id)arg; {
}
- (void)dealloc {
[super dealloc]; // expected-note {{[super dealloc] called here}}
[SuperDeallocThenCallObjectiveClassMethodClass invalidate:self]; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@ -132,13 +189,14 @@ static void _invalidate(NSObject *object) {
- (void)_invalidate {
}
- (void)dealloc {
if (_ivar) {
if (_ivar) { // expected-note {{Taking false branch}}
[_ivar release];
[super dealloc];
return;
}
[super dealloc];
[self _invalidate];
[super dealloc]; // expected-note {{[super dealloc] called here}}
[self _invalidate]; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
}
@end
@ -244,11 +302,15 @@ static void _invalidate(NSObject *object) {
// Do not warn about calling [super dealloc] twice if when the analyzer has
// inlined the call to its super deallocator.
@interface SuperClassCallingSuperDealloc : NSObject
@interface SuperClassCallingSuperDealloc : NSObject {
NSObject *_ivar;
}
@end
@implementation SuperClassCallingSuperDealloc
- (void)dealloc; {
[_ivar release]; // no-warning
[super dealloc];
}
@end
@ -291,8 +353,8 @@ static void _invalidate(NSObject *object) {
- (void)dealloc; {
[super dealloc]; // expected-note {{[super dealloc] called here}}
[self anotherMethod];
[super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
// expected-note@-1 {{[super dealloc] should not be called multiple times}}
[self anotherMethod]; // expected-warning {{use of 'self' after it has been freed with call to [super dealloc]}}
// expected-note@-1 {{use of 'self' after it has been freed with call to [super dealloc]}}
[super dealloc];
}
@end